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
Set your account-level Request webhook URL in the SuperDial portal: go to API, find the Webhooks section, and fill in Request webhook. Once configured, this becomes the default for every request you create. No per-requestwebhookUrl field needed.
To confirm delivery is working, the same panel shows Recent deliveries (7 days), filterable by Request ID. If you have submitted requests and that list is empty, the URL is not set correctly.
Per-request override
Pass awebhookUrl 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.
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 anX-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 depends on which credentials created the request:
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
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. Delivery has two layers. Inline retries. On a 5xx response or connection failure we retry up to 3 more times (4 attempts total) with exponential backoff (0.5s → 1s → 2s). Each attempt times out after 10 seconds. Later retries. If all four inline attempts fail, we try the delivery again later. A request gets up to 3 delivery rounds in total, within 7 days of reaching its terminal state. How we treat each response:
Once all three rounds are used, 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 samerequestId may arrive more than once. Always treat requestId as a dedup key:
internalId when creating the request, you have two layers of dedup keys. Pick whichever fits your system.
Recommended response
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. A400 or 404 stops delivery permanently (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).