- Webhooks are workspace-wide, not per-agent. One subscription receives
events for every agent you have. Filter on the payload’s
agent_idyourself. - Webhooks are regional. A subscription belongs to the region whose API created it and never fires for a call in another. If you run agents in both regions, create one in each. See regions.
Creating one
string
required
Where to POST. Must be
http:// or https://, and must resolve to a public
address — see what a URL must be.string[]
default:"[]"
Which types to receive. An empty list means every event type. Naming an
unknown type is a
400.string
Your own signing secret. Omit it and one is generated as
whsec_ followed by
43 URL-safe characters.status (active or disabled), last_status
(delivered or failed) and last_delivered_at, so a list says at a glance
which endpoint has stopped answering. PATCH /v1/webhooks/{webhook_id} changes
the URL, the subscriptions or the status; a disabled webhook receives nothing.
DELETE removes it permanently.
GET /v1/webhooks/event-types returns two lists: all, every type a
subscription will accept, and live, the types it reports as emitted today.
Every event type
Two notes on the agent events. Publishing fires
agent.published only; rolling
a version back fires both agent.updated and agent.published, because two
things moved — the draft is now that version’s config, and what live calls run
has changed. The email batch events are all about drafting: there is
deliberately no event for a send completing, because a send is fifty rows in a
few seconds.
The envelope
Every delivery has the same outer shape.
One envelope is built per event and sent to every matching webhook, so several
endpoints receiving the same event see the same
id.
session.completed in full
This is the one to subscribe to for a CRM. It carries the outcome, and it is the
only event that does. It fires for a text window as well as a call — every run
this platform bills or reports ends with exactly one of these.
The one exception is a run whose process died before it could finish. It is
never priced and never announced, so no session.completed arrives at all. (A
call an outbound batch placed is additionally failed stale four hours in, so
the campaign is not wedged by it.) That is another reason to reconcile from
GET /v1/calls rather than to trust that every ending arrives.
It is built identically whether the worker finishes it live or a reconcile
rebuilds it later, because it is assembled by reading the database rather than
from whatever a process happened to hold in memory. It cannot disagree with what
GET /v1/calls/{session_id} shows you a minute later.
There is no
call.transferred event. A transfer ends our session, so
session.completed fires immediately afterwards and carries every fact such an
event would have, in transfer: mode, transport, destination, outcome,
detail, sip_status, on_failure and at. Two events describing one fact
would be two things to keep in step.transfer is also what explains duration_s to a consumer. Both the duration
and the recording measure our part of the call; the caller may have stayed
with the person for another fifteen minutes.Why recording.ready is a separate event
It is not a subset of session.completed.
session.completed carries no link because it can be rebuilt hours after the
call, and a URL that dies in an hour would be a broken promise. Ask
GET /v1/calls/{session_id}/recording for a fresh one instead. See
recordings.
Verifying a delivery
Every request carries three headers:
The request also carries
Content-Type: application/json and
User-Agent: talqing-webhooks/1.
Delivery is best-effort
A slow endpoint also costs the run that triggered it. For the events a call worker sends as it shuts down, the whole fan-out is bounded at seven seconds and abandoned after that, so the process is never held open by an endpoint that stopped answering.The delivery log
GET /v1/webhooks/{webhook_id}/deliveries lists attempts newest first, in the
standard page shape. It is where to look when a webhook seems silent.
limit is 1–200, default 50.
Testing an endpoint
POST /v1/webhooks/{webhook_id}/test sends one webhook.test event and returns
what your endpoint said, so you can check delivery and signature verification end
to end before a real call depends on it.
- It bypasses the subscription filter.
webhook.testis a probe of the endpoint, not a type anyone subscribes to, so it arrives whateversubscribed_eventssays. - A disabled webhook is refused with a
400:webhook is disabled; enable it before testing. A disabled endpoint must not receive traffic.
{ "webhook_id": …, "status": …, "status_code": …, "error": … },
and the attempt is written to the delivery log like any other.
Rotating the secret
POST /v1/webhooks/{webhook_id}/rotate-secret issues a new secret and returns
it in plaintext once.
What a URL must be
The URL is checked when you configure it and again at every delivery.- The scheme must be
httporhttps. Anything else is a400:url must be http(s). - The host must resolve, and every address it resolves to must be public. A
private, loopback, link-local, reserved, multicast or carrier-grade-NAT
address is refused with a
400readingurl not allowed: destination ... is not allowed (private/internal). Cloud metadata endpoints are covered by the same rule. - Redirects are not followed. The connection is pinned to the address that passed the check, so a second DNS answer cannot point the request somewhere else after it was vetted. Point the webhook at its final URL.
blocked destination: ….
In the dashboard
Webhooks lists one row per subscription with its status, URL, subscribed types and the masked secret hint, and names the region it belongs to. Creating one shows the plaintext secret in a panel above the list — the only time it is shown. Each row has Test, Enable/Disable, Log (the delivery history inline) and Delete.Calls
The same facts
session.completed carries, on demand and always current.Call analysis
What fills the
analysis block of the payload.