Skip to main content
Webhooks let your backend react to what your agents do without polling. Register a URL and the events you care about, and AssemblyAI sends a signed POST to that URL whenever one fires. For phone calls, the payload includes ready-to-download recording and transcript URLs.
Base URL: https://agents.assemblyai.com. Send your API key in the Authorization header (a Bearer prefix is accepted), same as the rest of the API.

Set up a webhook handler

The reference section below covers every field and rule.
1

Generate a signing secret

Every delivery is signed with a secret you choose. Generate one, store it somewhere your handler can read it, and keep it. The API never returns it.
It must be 32 to 256 printable ASCII characters with no whitespace.
2

Write the handler

Your handler does two things: verify the signature, then acknowledge with a 2xx as fast as possible. Do any slow work after responding.
Run it: uvicorn receiver:app --port 8000 or node receiver.mjs.
Verify the raw request body bytes, exactly as shown above. If you parse the JSON and re-serialise it before hashing, the bytes differ and every signature fails with 401. A 401 is not retried, so those events are lost.
3

Expose it over HTTPS

The subscription URL must be https:// and publicly reachable; it’s checked when you create the subscription. For local development, put ngrok in front of your handler:
Copy the https://….ngrok-free.app URL it prints. In production, use your own HTTPS host.
4

Create the subscription

Register your URL, the events you want, and the secret from step 1. Pass agent_id to receive events for one agent, or leave it out for every agent on your account.
Save the id from the response. You need it to update or delete the subscription later.
Phone calls emit call.* events only, never session.*. If your agent answers phone calls, subscribe to call.connected, call.ended, and call.failed. A subscription to session events alone receives nothing for telephony. Use session.started and session.completed for WebSocket sessions.
5

Trigger an event and watch it arrive

Call your agent’s phone number, or start a WebSocket session against it, then end it. Within a few seconds your handler logs the delivery.If nothing arrives, check the delivery history. Every attempt is recorded against the session (for a call, the session id is the call_id):
A status of failed with "error": "HTTP 401: invalid signature" means your handler rejected it, usually because of the raw-bytes problem from step 2 or a secret mismatch. See Troubleshooting for the rest.
6

Use the event

What you do next depends on the channel:
  • Phone calls: call.ended includes recording_url and transcript_url. They’re presigned, work immediately, and expire one hour after the event, so download the files rather than storing the URLs. call.connected has both set to null.
  • WebSocket sessions: session.completed fires when the session ends, before the recording and transcript are written (s3_prefix is null in the payload). They typically appear within about 90 seconds. Fetch them with GET /v1/sessions/{session_id} from the Sessions API, polling every few seconds until artifacts is non-empty.
If you only need metadata (duration, close reason, agent, phone numbers), it is already in the webhook body. No follow-up request is needed.

Before you go to production

  • Deduplicate on event_id. Delivery is at-least-once, and the same event can arrive more than once. event_id is stable across retries; X-AAI-Delivery-Id changes per attempt, so don’t dedupe on it.
  • Respond within 15 seconds. Return 2xx first, do slow work after. A slow handler gets retried, which means duplicates.
  • Know what isn’t retried. Any 4xx other than 408, 425, and 429 is treated as your endpoint rejecting the event and is not retried. 5xx, timeouts, and connection errors are.
  • Don’t rely on ordering. call.connected is not guaranteed to arrive before call.ended.
  • Register the final URL. Redirects are not followed; a 301 counts as a failure.
  • Rotate secrets with PATCH. Sending a new secret bumps secret_version. Deliveries already in flight were signed with the old one, so accept both during the changeover.

Reference

Events

Delivery headers

Signature

  • t: Unix timestamp in seconds when the delivery was signed.
  • v1: hex HMAC-SHA256, keyed with your subscription secret, over the string "{t}." followed by the raw request body bytes.
Recompute v1, compare in constant time, and reject any delivery whose t is more than 300 seconds from your clock. The timestamp is what stops a captured request being replayed.

Payloads

Session events carry a session object:
session.started has the same shape with "status": "created" and duration_seconds, ended_at, and public_close_reason set to null. Call events carry a call object:
For a phone call, call_id and session_id are the same value. from_number is the caller’s real number, so treat the payload as personal data.

Subscription fields

Manage subscriptions

PATCH accepts any of url, events, secret, and enabled.

Delivery history

Each entry has status (pending, delivered, or failed), attempt_count, http_status_code, error, delivered_at, and next_retry_at. Deleting a subscription removes its entries.

Troubleshooting

Endpoints