Webhooks are the durable channel — at least once, signed, retried. For the same events pushed
live to a connected client (a UI or trading bot), use the WebSocket. See
Real time & data channels for the full channel map.
Configuring your endpoint
Set your endpoint URL and the events you want withPUT /v1/webhooks (managing the endpoint needs
account:read to view it, account:write to change it):
url— must be HTTPS and must not resolve to private/internal infrastructure.events— any subset of the catalog below. An empty array parks the endpoint (nothing matches) without deleting it.- Signing secret — generate one with
POST /v1/webhooks/rotate-secret. Deliveries only start once a signing secret exists (whsec_…, shown exactly once — store it securely). A typical setup isPUT /v1/webhooksthenPOST /v1/webhooks/rotate-secret.
You only receive an event if your key holds the scope that event requires (rechecked at
delivery time, not just at subscribe time). A
position.settled delivery needs positions:read;
a funds.* delivery needs balances:read.Market makers manage a separate endpoint with
?owner_kind=mm (gated on mm:quote). Its events
carry the MM’s side of a position — mm_result and, on buyback, mm_pays_user — rather than the
bettor’s. A user endpoint (the default) ships your own bettor side event data.Event catalog
parlay.status_changed tracks an RFQ’s live progression — status is one of accepted,
confirmed, executed, or cancelled (accepted_quote_id is set once a quote is accepted, else
null). Terminal settlement comes via position.settled, and expiry isn’t part of this event,
so use the position.* events for final outcomes. A cancellation fires both
parlay.status_changed (status: "cancelled") and position.cancelled — dedupe on
X-Totalis-Event-Id if you subscribe to both.| Event | Required scope | data fields |
|---|---|---|
position.settled | positions:read | rfq_id, outcome, tx_signature, occurred_at |
position.cancelled | positions:read | rfq_id, reason, occurred_at |
position.bought_back | positions:read | rfq_id, amount (the directional buyback amount, net on top of the refunded stake — see mm_pays_user for direction), mm_pays_user, tx_signature, occurred_at |
parlay.status_changed | positions:read | rfq_id, status, accepted_quote_id, occurred_at |
funds.deposited | balances:read | amount_usdc, vault_balance, occurred_at |
funds.withdrawn | balances:read | amount_usdc, vault_balance, occurred_at |
Delivery format
Each delivery is an HTTPPOST with a JSON body and these headers:
| Header | Description |
|---|---|
X-Totalis-Signature | t=<unix-seconds>,v1=<hmac-sha256-hex> — see verifying. |
X-Totalis-Event-Id | Unique event id. Use this to deduplicate. |
X-Totalis-Event-Type | The event name, e.g. position.settled. |
Content-Type | application/json |
id, type, and an event specific data object:
2xx status to acknowledge. Anything else is treated as a failure and retried.
Verifying signatures
TheX-Totalis-Signature header has the form t=<timestamp>,v1=<signature>, where the signature is
HMAC-SHA256(secret, "<timestamp>.<raw-body>") hex encoded. To verify:
- Parse
tandv1from the header. - Reject if
tis outside your tolerance (±5 minutes recommended) — this blocks replay. - Recompute the HMAC over the exact raw request body joined to the timestamp as
t.body. - Compare to
v1with a constant time equality check.
Idempotency & ordering
- Deduplicate on
X-Totalis-Event-Id. At least once delivery means you may occasionally receive the same event more than once (a retry that actually succeeded, or a replay). Treat the event id as the unique key. - Don’t assume ordering. Process each event on its own merits; use
occurred_atif you need to reason about sequence.
Retries & failure handling
If your endpoint doesn’t return2xx, Totalis retries with exponential backoff:
- Retried:
5xx,408,429, timeouts, and network errors → statusfailed, will retry. - Not retried: other
4xxresponses → immediatelydead_letter(your endpoint rejected it; a retry won’t help). - After the retry budget is exhausted, the delivery is
dead_letter.
2xx quickly and do heavy work asynchronously — a slow endpoint that exceeds the delivery
timeout counts as a failed attempt.
Inspecting & replaying deliveries
GET /v1/webhooks/deliveries lists recent deliveries with their status, attempt count, and last
response code (newest first):
Replaying deliveries
A delivered or dead lettered delivery can be requeued withPOST /v1/webhooks/deliveries/{id}/redeliver — useful after you fix a bug or an outage on your side.
Replays reuse the same X-Totalis-Event-Id, so your deduplication will recognize them.
A
failed delivery that is still retrying can’t be manually replayed — it’s already scheduled for
another attempt. Wait for it to land or be dead lettered.Rotating your signing secret
Rotate the secret anytime withPOST /v1/webhooks/rotate-secret. The new secret is shown once and the
old one stops signing immediately — there’s no overlap window where both are valid. A safe
sequence:
Rotate during a quiet window
Generate the new secret. Capture it immediately — it’s shown only once.
Update your verifier
Deploy the new secret to your endpoint as fast as possible. Any deliveries signed with the new
secret that arrive before your verifier is updated will fail verification.
