Skip to main content
When a request reaches a terminal state, SuperDial sends an HTTP POST to your webhook endpoint with the result. Webhooks are the recommended way to consume request results.

When webhooks fire

A webhook is sent once per request, when its state becomes one of: No webhook is sent while a request is still PROCESSING.

Configuration

You can set your webhook endpoint in two places:

Account default

Ask your account team to set an account-level webhook URL. Once configured, this becomes the default for every request you create. No per-request webhookUrl field needed.

Per-request override

Pass a webhookUrl field in the body of POST /v1/requests to direct a single request to a different URL. The per-request value always wins over the account default.
If neither is configured, no webhook is sent and you must poll GET /v1/requests/{requestId} for results.

Payload

The webhook body is intentionally compact. To get the full result (results, missingFields, modality, data_completeness, and the error object on non-SUCCESS), call GET /v1/requests/{requestId} after receiving the webhook.
By the time you receive the webhook, the request is fully readable. For phone-backed requests, the call enrichment fields (transcript, recordingDownloadUrl, callDuration, callSummary) are available on the GET /v1/requests/{requestId} response.

Signature verification

Every webhook delivery includes an X-Webhook-Signature header. Verify it on every delivery. Without verification, anyone who learns your endpoint URL can forge events. The signature is HMAC-SHA256 of the raw request body. The signing secret is your account’s webhook secret if you’ve set one up on the portal, otherwise it is your production API key. The same production-side secret is used for sandbox webhooks too. Your sandbox API key will fail verification. Compute the HMAC over the raw body bytes exactly as received: do not parse and re-serialize the JSON, since that changes the byte sequence and breaks the signature. The header value is the bare 64-character lowercase hex digest, with no sha256= prefix or other framing. Example:

Python

Node.js

Both samples use a constant-time comparator (hmac.compare_digest / crypto.timingSafeEqual); use the equivalent in your language rather than == to avoid timing-attack risk. The signature doesn’t include a timestamp, so it can’t be used to detect replays on its own. Use requestId as a dedup key (see Idempotency below).

Delivery and retries

We send the webhook when the request reaches a terminal state. On a 5xx response or connection failure we automatically retry up to 3 more times (4 attempts total) with exponential backoff (0.5s → 1s → 2s). A 4xx response is treated as terminal: we don’t retry, since re-posting the same payload is unlikely to succeed. If all four attempts fail, the failure is recorded and no further attempts are made. Fall back to polling GET /v1/requests/{requestId} to recover. For high-reliability ingestion:
  • Ack with 2xx as soon as you’ve durably enqueued the event for processing.
  • Track the requestIds you’ve submitted on your side. Any request whose terminal outcome you haven’t received within the expected window should trigger a fallback poll.

Idempotency

In rare cases the same requestId may arrive more than once. Always treat requestId as a dedup key:
If you also passed internalId when creating the request, you have two layers of dedup keys. Pick whichever fits your system. Return HTTP 200 as quickly as possible. Don’t do synchronous work that could exceed our 10-second timeout. Push processing to a background job and ack immediately. 5xx responses and connection failures trigger automatic retries; 4xx responses don’t (see Delivery and retries above). If all retries are exhausted, fall back to polling GET /v1/requests/{requestId} to recover. After acking, call GET /v1/requests/{requestId} to fetch the full payload (results, missingFields, modality, and the error object on FAILURE).