Skip to main content
When a call ends, TopCalls sends a JSON POST to your server with the call details, transcript, recording and analysis. That happens whether the call completed, failed or was cancelled, so you don’t need to poll.

Setting Up Webhooks

You can set a webhook in three places.

Per-Call Webhooks

Set webhook_url when you create a call:
With webhook_call_status_filter, the webhook fires only when the call’s call_status (the telephony outcome, such as completed, no-answer or busy) is in the list. Leave it out or empty to get every outcome. Subscriptions ignore it.

Campaign Webhooks

Set config.webhook_url on a campaign, or the webhook field in the campaign editor, and every call it places or answers sends there. config.webhook_call_status_filter works as above.

Account-Level Subscriptions

A subscription gets every matching call on your account, however the call was created:
call.completed fires for every call that ends, failed and cancelled ones included, so check status in the payload. To get a single disposition, add its slug: the label lowercased, with each run of other characters replaced by _. A call labelled Booked Callback fires call.completed.booked_callback:
filters narrow it further, and every key has to match. A plain value matches by equality, an array matches any of its strings, and a string with % is a SQL LIKE pattern (% is any run of characters, _ is one). The key is_conversion matches calls whose disposition is in the campaign’s conversion outcomes, ignoring case:
Any payload field works as a key. "direction": "inbound", for example, keeps only calls that came in on your numbers. GET /v1/webhooks lists your subscriptions. GET, PATCH and DELETE /v1/webhooks/{webhook_id} read, change or remove one. Full schemas are in the API reference. Test calls from the dashboard’s browser tester never send webhooks.

Webhook Payload

A complete call.completed payload:
status is completed, failed or cancelled. duration is in minutes. Each transcript item has an id, a speaker, text and a created_at timestamp with milliseconds. Here the speaker field is role (assistant or user); GET /v1/calls/{call_id} calls the same field user. Spoken turns have no kind. Other items set it:
  • tool_call: the agent used a tool (tool_name, tool_status of success, failure or unknown, and tool_ok, which is false only on a reported failure and doesn’t prove the action worked)
  • handoff: one crew member passed the call to another (handoff_from, handoff_to)
  • signal: the caller went quiet (caller_silent) or the call is near its time limit (caller_time_limit), named in signal, with signal_seconds when known
  • barge_in: the caller spoke over the agent
  • end: how the call finished, in end_via: agent, caller, time_limit, voicemail or connection
signal and barge_in items appear only on legacy-mode calls; a Live call never sends them. To keep only speech, skip items that have kind. disposition is the label post-call analysis gave the call, or a platform label: Voicemail, Transferred or No Response. is_conversion is true when that label is in the campaign’s conversion outcomes (case doesn’t matter) and always false on failed calls. direction is outbound when TopCalls placed the call and inbound when it came in on one of your numbers. Either way, phone_number is the other party. caller_name shows up only on inbound payloads: the name the caller’s network sent, or null. If the agent tried a transfer to a person, the payload also carries transferred_to and transfer_status.

Webhook Timing

A completed call’s webhook goes out once the transcript, summary and recording are ready, at least 15 seconds after hang-up. It waits up to about 2.5 minutes for the recording. If the recording still isn’t ready, the webhook goes without recording_url, and nothing adds it later, so GET /v1/calls/{call_id} won’t have it either. Failed and cancelled calls have no recording to wait for, so their webhook goes out right away.

Handling Webhooks

Express.js Example

Securing Your Endpoint

Payloads aren’t signed, so the URL itself is the credential:
  • Serve the endpoint over HTTPS only.
  • Put an unguessable token in the path or query string (for example https://your-app.com/webhooks/tc_8f3k2m9x) and reject requests without it.
  • To double-check a payload before acting on it, fetch GET /v1/calls/{call_id} and compare.

Webhook Retries

Your endpoint has 10 seconds to answer with a 2xx. A 4xx on any attempt is final and never retried. A timeout, network error or 5xx is retried up to 5 times, 1, 5, 15, 30 and then 60 minutes after the previous failure, for 6 attempts in all. If the last one fails, the delivery is dropped. The call itself is still there in GET /v1/calls/{call_id}.

Best Practices

  • Answer with a 2xx right away and process in the background. Slow work inside the handler runs into the 10-second timeout.
  • Expect the same call more than once and deduplicate on call_id.
  • Catch errors in your handler so one bad payload can’t crash the server.
  • Log what you receive. It makes debugging much easier.
  • Keep the URL secret: HTTPS, an unguessable token, and never in public logs or repositories.

Testing Webhooks

To test locally, expose your server with ngrok or point the webhook at webhook.site:
Then use the tunnel URL as the webhook:

Next Steps

Webhooks & Events

Learn about available webhook events and when they fire.

API Reference

See complete webhook payload schemas in the API reference.