Developers

Webhooks

An Admin adds webhooks on API clients and webhooks: an HTTPS URL on a host of the firm's webhook allow-list, and the events to send. Vestoris refuses URLs that resolve to private, loopback or reserved addresses (checked again at every delivery, with the connection pinned to the checked address) and does not follow redirects.

EventWhen
order.createdAn order was created (blotter, pipeline, rebalance or the API)
order.updatedAn order changed: amended, approved, rejected, released, cancelled, executed or reworked
fill.createdA simulated fill was recorded
violation.createdA compliance violation was raised (pre-trade, in-trade or post-trade)
violation.updatedA violation was acknowledged, resolved, waived or escalated
workflow.item.updatedA workflow item was approved in part, escalated by hand, re-prioritized or moved
workflow.sla_breachedA workflow item passed its SLA and was escalated automatically
feed.run.updatedA feed file arrived, was applied, held, rejected, failed, or is late or missed
sod.statusThe start-of-day chain changed state (running, blocked, complete, overridden)
rule.activatedA compliance or workflow rule was signed off and activated
rule.retiredA compliance or workflow rule was retired
pingA test event sent from the webhook page or POST /webhooks/{id}/ping

The delivery

POST /your/endpoint HTTP/1.1
Content-Type: application/json
Vestoris-Event: order.updated
Vestoris-Delivery: 1234
Vestoris-Event-Id: evt_6f1c...
Vestoris-Signature: t=1792980000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

{"id":"evt_6f1c...","type":"order.updated","created_at":"2026-10-26T14:05:00Z","firm_id":"3","paper_trading":true,"data":{...}}

Answer with any 2xx within 5 seconds. Otherwise Vestoris retries after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marks the delivery dead. Every attempt is in the delivery log; any delivery can be sent again. Deliveries can arrive more than once or out of order: use id to deduplicate.

Verifying the signature

The signature is HMAC-SHA256 with your webhook's signing secret (whsec_..., shown once) over "<t>.<raw body>". Check it with a constant-time comparison and refuse a t more than 300 seconds from now (the replay window).

PHP:

function vestoris_verified(string $secret, string $header, string $body, int $window = 300): bool {
    if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $header, $m)) return false;
    if (abs(time() - (int) $m[1]) > $window) return false;
    return hash_equals(hash_hmac('sha256', $m[1] . '.' . $body, $secret), $m[2]);
}
$ok = vestoris_verified(getenv('VESTORIS_WEBHOOK_SECRET'), $_SERVER['HTTP_VESTORIS_SIGNATURE'] ?? '', file_get_contents('php://input'));

Python:

import hmac, hashlib, re, time

def vestoris_verified(secret: str, header: str, body: bytes, window: int = 300) -> bool:
    m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
    if not m or abs(time.time() - int(m.group(1))) > window:
        return False
    mac = hmac.new(secret.encode(), m.group(1).encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, m.group(2))

Node.js:

const crypto = require('crypto');
function vestorisVerified(secret, header, rawBody, windowSec = 300) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > windowSec) return false;
  const mac = crypto.createHmac('sha256', secret).update(m[1] + '.').update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(m[2]));
}

Verify the raw body, before any JSON parsing. Keep the secret in your secret manager; "New secret" on the webhook replaces it at once.

API referenceAll developer pages