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 (when a call connects, a session ends, and so on). This is how you log calls, kick off post-call processing, or update your own systems for agents you created over REST. Base URL: https://agents.assemblyai.com. Manage subscriptions with your API key in the Authorization header, same as the rest of the API.

Events

EventFires when
session.startedA voice agent session begins.
session.completedA session ends (delivered with final duration and close reason).
call.connectedAn inbound phone call connects to an agent.
call.endedA call ends.
call.failedA call fails to connect or errors out.

Create a subscription

POST /v1/webhook-subscriptions. Subscribe to one or more events; scope to a single agent with agent_id, or omit it to receive events for all your agents.
curl -X POST https://agents.assemblyai.com/v1/webhook-subscriptions \
  -H "Authorization: $ASSEMBLYAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/assemblyai",
    "events": ["session.completed", "call.ended"],
    "secret": "a-long-random-signing-secret-at-least-32-chars",
    "agent_id": "7ad24396-b822-4dca-871a-be9cc4781cf9"
  }'
{
  "id": "0d75ebd7-937c-495c-b638-ba582ec05b39",
  "agent_id": "7ad24396-b822-4dca-871a-be9cc4781cf9",
  "url": "https://your-app.com/webhooks/assemblyai",
  "events": ["session.completed", "call.ended"],
  "enabled": true,
  "secret_version": 1,
  "created_at": "2026-06-10T14:16:59Z",
  "updated_at": "2026-06-10T14:16:59Z"
}
FieldRequiredNotes
urlYesWhere deliveries are POSTed. Must be https and a public host.
eventsYesOne or more of the events above. No duplicates.
secretYesYour signing secret, 32-256 printable ASCII chars (no whitespace). Used to sign every delivery; store it, since it’s never returned.
agent_idNoScope to one agent. Omit for account-wide events.
enabledNoDefaults to true. Set false to pause deliveries.
The response returns a secret_version (an integer that increments when you rotate the secret), never the secret itself. Keep your own copy of the secret to verify signatures.

Receiving a delivery

When an event fires, AssemblyAI sends a POST to your url with these headers:
HeaderValue
X-AAI-EventThe event type, e.g. session.completed.
X-AAI-Signaturesha256=<hex HMAC-SHA256 of the raw body, keyed by your secret>.
X-AAI-Delivery-IdUnique per delivery attempt; use it to dedupe.
Session events carry a session object:
{
  "event_id": "f1d2...",
  "event": "session.completed",
  "timestamp": "2026-06-10T14:20:31Z",
  "session": {
    "session_id": "sess_abc123",
    "account_id": 325374,
    "agent_id": "7ad24396-b822-4dca-871a-be9cc4781cf9",
    "status": "completed",
    "duration_seconds": 142.6,
    "public_close_reason": "client_ended",
    "s3_prefix": "…",
    "created_at": "2026-06-10T14:18:08Z",
    "ended_at": "2026-06-10T14:20:31Z"
  }
}
Call events carry a call object (with recording/transcript links where available):
{
  "event_id": "a93c...",
  "event": "call.ended",
  "timestamp": "2026-06-10T14:20:32Z",
  "call": {
    "call_id": "call_8b2b1e8c...",
    "account_id": 325374,
    "agent_id": "7ad24396-b822-4dca-871a-be9cc4781cf9",
    "status": "completed",
    "direction": "inbound",
    "from_number": "+14155550123",
    "to_number": "+19016659697",
    "session_id": "sess_abc123",
    "recording_url": "https://…",
    "transcript_url": "https://…",
    "created_at": "2026-06-10T14:18:05Z",
    "ended_at": "2026-06-10T14:20:32Z"
  }
}
Respond with a 2xx status promptly. Failed deliveries are retried with backoff, so make your handler idempotent: dedupe on event_id (or X-AAI-Delivery-Id).

Verify the signature

Always verify X-AAI-Signature before trusting a delivery. Compute HMAC-SHA256 over the exact raw request body (don’t re-serialize the JSON) using your subscription secret, and compare in constant time:
import hashlib, hmac

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")

# Flask example
@app.post("/webhooks/assemblyai")
def handle():
    if not verify(request.get_data(), request.headers.get("X-AAI-Signature"), SECRET):
        return "", 401
    event = request.get_json()
    # ... handle event["event"], event["session"] / event["call"]
    return "", 200

Manage subscriptions

# List (paginated, newest first)
curl https://agents.assemblyai.com/v1/webhook-subscriptions -H "Authorization: $ASSEMBLYAI_API_KEY"

# Retrieve one
curl https://agents.assemblyai.com/v1/webhook-subscriptions/{id} -H "Authorization: $ASSEMBLYAI_API_KEY"

# Update: change the URL/events, pause, or rotate the secret (bumps secret_version)
curl -X PATCH https://agents.assemblyai.com/v1/webhook-subscriptions/{id} \
  -H "Authorization: $ASSEMBLYAI_API_KEY" -H "Content-Type: application/json" \
  -d '{ "enabled": false }'

# Delete
curl -X DELETE https://agents.assemblyai.com/v1/webhook-subscriptions/{id} -H "Authorization: $ASSEMBLYAI_API_KEY"
PATCH accepts any of url, events, secret, enabled. Sending a new secret rotates it and increments secret_version.

Endpoint summary

MethodPathDescription
POST/v1/webhook-subscriptionsCreate a subscription.
GET/v1/webhook-subscriptionsList your subscriptions.
GET/v1/webhook-subscriptions/{id}Retrieve one.
PATCH/v1/webhook-subscriptions/{id}Update (url, events, secret, enabled).
DELETE/v1/webhook-subscriptions/{id}Delete.