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 (re-checked 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.Delivery format
Each delivery is an HTTPPOST with a JSON body and these headers:
The body is a stable envelope —
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 (we recommend ±5 minutes) — 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 re-queued 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 mid-retry can’t be manually replayed — it’s already scheduled for
another attempt. Wait for it to land or dead-letter.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:
1
Rotate during a quiet window
Generate the new secret. Capture it immediately — it’s shown only once.
2
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.
3
Replay anything that failed in the gap
From the deliveries list, replay any
dead_letter (or, once they exhaust retries, formerly
failed) deliveries from the swap window — they’ll be re-signed with the current secret.
