Skip to main content
ThunderPhone sends HTTP POST requests to your server when things happen during a call — an inbound call starts, a call ends, a grading run completes, an alert fires, and so on. There are two delivery models:

Webhook endpoints (recommended)

Multiple URLs, per-endpoint secrets, per-endpoint event filters, and automatic retries. Manage via GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.

Single-URL legacy webhook

One URL per org. Carries the call-lifecycle events, including the blocking configuration exchanges. Managed at GET/PUT /v1/webhook.
All ten event types in the events catalog are delivered through webhook endpoints. The six call-lifecycle events (telephony.incoming, telephony.complete, telephony.tool, web.incoming, web.complete, web.tool) are also sent to the legacy single-URL webhook — if you have both a legacy URL and a matching endpoint, you receive the event on both paths. Blocking behavior (the telephony.incoming / web.incoming configuration exchange and webhook-mode tool dispatch) lives exclusively on the legacy path; every endpoint delivery is a fire-and-forget notification.

Payload format

Endpoint deliveries are a JSON object with data, event_id, and type:
event_id is unique per emitted event. It is identical across retries and across every endpoint that receives the event — dedup on it. The legacy single-URL webhook sends the same type and data but without event_id:
On the wire, every body is serialized canonically — keys sorted alphabetically, no whitespace, UTF-8. The pretty-printed examples in these docs are for readability only. See the Events catalog for the full list of event types and payload fields.

Signature verification

Every request carries an HMAC-SHA256 signature over the raw request body in the X-ThunderPhone-Signature header. The signing key is the endpoint’s secret (or your org-level webhook secret for legacy deliveries).

Steps

  1. Read the raw request body before any parsing.
  2. Compute hmac_sha256(secret, body).hexdigest().
  3. Compare in constant time to the X-ThunderPhone-Signature header.
We sign exactly the bytes we transmit, and those bytes are the canonical JSON serialization (sorted keys, compact separators). So verifying against the raw body always works — and if your framework only hands you parsed JSON, re-serializing it with sorted keys and compact separators produces the identical bytes. Both recipes are covered in the verification guide.

Delivery semantics

These semantics apply to endpoint deliveries. The legacy single-URL webhook is a single synchronous attempt with no retries.
Each event is attempted once immediately. Any 2xx response acknowledges the delivery. On any other outcome (non-2xx, connection error, timeout) we retry at 1 m, 5 m, 30 m, 2 h, 6 h, 12 h, and 24 h after the first attempt — 8 attempts spanning 24 hours. If every attempt fails, delivery stops and the endpoint is marked status="failing" in webhook endpoints. Return 2xx as soon as the payload is durably accepted; process asynchronously.
Delivery ordering is best-effort. In practice we deliver in the order events are emitted, but retries can reorder on failure. Always dedup and reconcile by call_id / object id.
Delivery is at-least-once: a retry after a response we never saw can duplicate an event. Every retry carries the same event_id, so store processed ids and skip repeats. event_id is also shared across endpoints — two endpoints subscribed to the same event receive the same event_id.
Endpoint deliveries have a 30 s timeout per attempt. On the legacy path, blocking requests that drive live call behavior — the telephony.incoming / web.incoming configuration exchange — time out after 10 s, but a slow response delays call pickup, so aim to answer within a couple of seconds. Webhook-mode tool dispatch allows 20 s.
Outbound webhooks originate from ThunderPhone’s cloud IP range. If your firewall requires an allowlist, contact support and we’ll share the current ranges.

Choosing between legacy and endpoint-based webhooks

New integrations should consume events through endpoint-based webhooks. Keep (or add) a legacy URL only if you configure calls dynamically at pickup time or use webhook-mode tool dispatch — those request/response exchanges only run on the legacy path.

Events catalog

All event types and their payloads.

Webhook endpoints

Manage multiple endpoints, event filters, and secrets.

telephony.incoming / web.incoming

The blocking request your server must answer to configure calls.

telephony.complete / web.complete

Post-call payload with transcript, recording, and metrics.