How it works
- You subscribe to the
telephony.incoming(phone) orweb.incoming(widget) event. Both are blocking webhooks: ThunderPhone waits up to 10 seconds for your response before continuing the call. - ThunderPhone sends you
{call_id, from_number, to_number}(widget sessions carry widget-specific fields instead of numbers — see the request schema). - 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), whether delivered to a webhook endpoint
or to the legacy single-URL webhook.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. Ten seconds is the hard cap, and every second is dead air for the caller. 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 the incoming-call response schema exactly. The commonly-used fields: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
Before you hand-roll this, note that ThunderPhone has a native Experiments feature (/dashboard/experiments and the agent builder’s A/B tab) that
defines variants, splits traffic, and compares outcomes per variant —
no webhook required.
If you need webhook-side control anyway: hash call_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 the
completed call’s grade.
Time-based routing
Business hours → “live support” agent; after-hours → “take a message” agent. Pure switch onnew Date().getUTCHours() in your handler.
Next steps
Incoming-call webhook 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.