Skip to main content
Webhooks push events to your server as they happen, so you don’t have to poll. Each event is HMAC-signed so you can verify it came from Totalis, delivered only for events whose scope your API key holds, and retried with backoff until it succeeds or is dead-lettered. The endpoint belongs to your account (the principal), not to the key that created it — any of your active keys with the right scope manages it, and revoking one key among several does not stop delivery.
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 with PUT /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 is PUT /v1/webhooks then POST /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 HTTP POST with a JSON body and these headers: The body is a stable envelope — id, type, and an event-specific data object:
Respond with any 2xx status to acknowledge. Anything else is treated as a failure and retried.

Verifying signatures

The X-Totalis-Signature header has the form t=<timestamp>,v1=<signature>, where the signature is HMAC-SHA256(secret, "<timestamp>.<raw-body>") hex-encoded. To verify:
  1. Parse t and v1 from the header.
  2. Reject if t is outside your tolerance (we recommend ±5 minutes) — this blocks replay.
  3. Recompute the HMAC over the exact raw request body joined to the timestamp as t.body.
  4. Compare to v1 with a constant-time equality check.
Compute the HMAC over the raw bytes of the request body, before any JSON parsing or re-serialization. Re-stringifying parsed JSON can reorder keys or change whitespace and will break the signature.

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_at if you need to reason about sequence.

Retries & failure handling

If your endpoint doesn’t return 2xx, Totalis retries with exponential backoff:
  • Retried: 5xx, 408, 429, timeouts, and network errors → status failed, will retry.
  • Not retried: other 4xx responses → immediately dead_letter (your endpoint rejected it; a retry won’t help).
  • After the retry budget is exhausted, the delivery is dead_letter.
Return 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 with POST /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 with POST /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.