How it works
- You subscribe to the
telephony.incoming(phone) orweb.incoming(widget) event. Both are blocking webhooks: ThunderPhone waits up to two seconds for your response before continuing the call. - ThunderPhone sends you
{call_id, from_number, to_number}. - Your server responds with an agent configuration (prompt, voice, product, tools). ThunderPhone uses that configuration for the call.
- If you return
{}, time out, or error, the statically-assigned agent is used as a fallback. Safe default.
Works identically for phone calls (
telephony.incoming) and widget
sessions (web.incoming). The legacy single-URL webhook uses the
event name call.incoming for both.1. Configure the webhook destination
- Phone calls
- Web widget
For phone numbers, subscribe your endpoint to The response includes a one-shot
telephony.incoming:secret — save it; you’ll use it
for signature verification.2. Implement the handler
Three rules of thumb:- Verify the signature on every request (see Verify webhook signatures). Don’t skip this in dev — get it right once and reuse.
- Respond fast. Two seconds is the hard cap. Do database lookups if you need to, but don’t call downstream LLMs synchronously — if you want dynamic prompt generation, pre-compute and cache.
- Fall back cleanly. Any unexpected state should return
{}so the statically-assigned agent handles the call.
3. Response schema
The response body matches thecall.incoming schema
exactly. The commonly-used fields:
| Field | Type | Description |
|---|---|---|
prompt | string (required) | System prompt for the agent |
voice | string (required) | Voice id from GET /v1/voices |
product | string | Defaults to spark |
background_track | string | null | Ambient audio id |
acknowledgement_prompt_mode | string | auto or manual (Storm-with-ack only) |
acknowledgement_prompt | string | Required when mode is manual |
tools | array | Inline function-tool schemas — see Function Tools |
Per-call speak-order and
max_hold_seconds aren’t available on the
webhook response. Set them on the
Agent you reference.Patterns
Logged-in user context
In webhook-mode widgets, the visitor’s page already knows who they are. Call your webhook with a query string parameter the widget SDK forwards (?customer_id=123) and look up the customer server-side.
A/B prompt rollout
Hashcall_id → bucket; serve prompt A for 0..49 and prompt B for
50..99. Record which bucket you chose in your own DB and later
correlate against call.complete’s grade_score.
Time-based routing
Business hours → “live support” agent; after-hours → “take a message” agent. Pure switch onnew Date().getUTCHours() in your handler.
Next steps
call.incoming reference
Exact request + response schemas, including every configuration key.
Verify webhook signatures
Get the HMAC right once; reuse everywhere.
Build a tool integration
Combine dynamic routing with per-agent tools.
Delivery semantics
Retries, ordering, timeouts.