Skip to main content

Scoping & Event Filtering

By default every webhook endpoint you configure is account-wide: it receives events from every agent on your account. Two controls narrow that down.
  • Scope — bind an endpoint to a single agent, so it receives only that agent’s events.
  • Event allowlist — subscribe an endpoint to specific event types, so everything else is filtered out.
They are independent, and they compose.

Account vs agent scope

An endpoint created without agent_id is account-wide. Pass agent_id at creation to bind it to one agent:
POST /api/v2/agent/{agent_id}/webhooks does the same thing with the scope taken from the path; any agent_id in the body is ignored there. Edit, delete, rotate, test, and delivery history stay on /config/webhooks/{id} for both — an endpoint id is already unambiguous.
agent_id is set once and cannot be changed. PATCH rejects it with 400 VALIDATION_ERROR, even when the value matches the endpoint’s current scope. Re-pointing a live endpoint at a different agent would silently change what an already-running integration receives, with nothing on the receiving end to signal it. Delete the endpoint and create a new one instead.
An agent_id that is not one of your own agents — or belongs to a deleted agent — is rejected with 404 NOT_FOUND, not 403, so this route cannot be used to probe whether an agent id exists on someone else’s account.

Limits are per scope; labels are per account

These two are counted differently, and it is easy to assume otherwise.
You cannot label each agent’s endpoint prod. The uniqueness constraint is on (account, label), not (scope, label), so the second agent to claim prod gets 409 LABEL_TAKEN.Qualify agent-scoped labels instead — prod-acme, prod-globex — or name them after the tenant rather than the environment.
Deleting an agent does not delete its endpoints. DELETE /api/v2/agent/{id} is a soft delete, so the agent’s endpoint rows survive it. They keep holding their labels and keep counting against that agent’s 5, while GET/PATCH /agent/{id}/webhooks* start returning 404 NOT_FOUND because the agent is gone.Delete the endpoints yourself with DELETE /config/webhooks/{id} — that route works on an endpoint whose agent has been deleted — before you retire an agent, or you will strand labels you cannot reuse.

Agent-scoped endpoints are additive

An agent-scoped endpoint does not replace your account-wide ones. It adds to them. Given: …a session.completed on agent a1b2c3d4… produces two independent deliveries — one to prod, one to acme-tenant. Each has its own signature, its own retry schedule, and its own row in delivery history. One failing does not affect the other. An event on any other agent reaches only prod.

Turning off account-wide fan-out

Additive is the right default, but not always what you want. An agent that feeds a different downstream system — a white-label tenant, a client migrating from another platform, a one-off integration — should be able to stop duplicating its traffic into your main integration. webhooks_include_account_endpoints is a per-agent flag, true by default:
The response echoes the stored value:
Read the current value along with the agent’s endpoints:
endpoints lists only this agent’s own endpoints — deliberately, so you can see which rows actually live on the agent. It does not fold in the account-wide ones even when include_account_endpoints is true. available_events is the live event catalog; read it from here rather than hard-coding the list.
The flag only applies to events that carry an agent. An event with no agent attached always goes to your account-wide endpoints — honouring a stray false there would disable webhooks account-wide.

The guard: 409 NO_AGENT_ENDPOINT

Setting the flag to false is refused while the agent has no enabled endpoint of its own:
Create the agent’s endpoint first, then turn the flag off. The same protection runs in reverse: if you later delete or disable the agent’s last enabled endpoint, the flag automatically flips back to true rather than leaving the agent with nowhere to deliver. The bias is deliberate — an unexpected duplicate is an annoyance you can dedupe, a dropped event is unrecoverable.

Event allowlist

events is a per-endpoint allowlist. Omit it, or send [], and the endpoint receives every event type — including ones added to the platform later.
An unknown event name is rejected with 400 VALIDATION_ERROR listing the valid set, so a typo fails loudly at configuration time rather than silently delivering nothing. Duplicates are de-duplicated rather than rejected.

Event catalog

Six event types, and this is the complete list.
There is no session.failed event. A session that ends in a terminal failure arrives as session.completed with duration_seconds: 0, transcript: null, and a terminal call.status — see How a failed session surfaces.It was previously published here with no producer behind it, so an endpoint that allowlisted it silently received nothing for that type. It is now rejected with 400 VALIDATION_ERROR, and existing allowlists were stripped of it automatically. An endpoint left with an empty allowlist receives every event type.
webhook.test is deliberately not in this list. It is fired only by the test endpoint and always reaches the endpoint being tested, regardless of its allowlist — otherwise “send test” would silently do nothing on exactly the endpoints most likely to be misconfigured. You still receive it, so handle or ignore it explicitly.

How the two controls combine

Both filters must pass for a delivery to happen:
  1. Scope decides which endpoints are candidates for this event.
  2. Allowlist decides whether each candidate accepts this event type.
Filtering is strict when account fan-out is off. With include_account_endpoints: false, an event whose type is not in any of the agent’s endpoint allowlists is dropped — it is not forwarded to your account-wide endpoint as a fallback.This is intentional. Falling back would re-deliver exactly the traffic you turned the flag off to stop, and leakage into an integration you were trying to quiet is the failure mode the feature exists to prevent. It also keeps “where does this event go?” answerable from the agent’s flag and its own endpoints alone.
This only bites when you use both features at once. An endpoint with an empty events array — the default — receives every type, so the interaction never arises. Worked example. Agent a1b2c3d4… has include_account_endpoints: false and one endpoint subscribed to ["session.completed"]: To receive call.transferred too, either add it to the endpoint’s events, add a second agent endpoint that subscribes to it, or turn include_account_endpoints back on.

Webhooks vs lifecycle hooks

These are different mechanisms and people reach for the wrong one. If you want to know something happened, use a webhook. If you want to fetch or push data as part of the call, use a lifecycle hook.

Next steps

Webhook Setup

Create endpoints, verify signatures, and send test events.

Webhook Payloads

Full payload structure for each event type.