A webhook is a URL Talqing POSTs a signed JSON event to when something happens in your workspace — a call ended, an agent was published, a tool failed, a batch stopped. It is how a CRM learns about a call without polling. Two things about them decide most of your integration:
  • Webhooks are workspace-wide, not per-agent. One subscription receives events for every agent you have. Filter on the payload’s agent_id yourself.
  • 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.
The signing secret is shown exactly once, in the response to create and to rotate. Every later read returns only secret_hint, a masked tail. If you lose it, rotate — there is no way to read it back.
A webhook row also carries 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.
live is currently missing two types that your endpoint will nonetheless receive: session.queued and session.in_progress. Both are dispatched on phone calls, and neither list gates delivery — only this listing. If you subscribed to everything, expect them. They are in the table below.

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.
The signed bytes are the raw request body, exactly as sent. Verify before you parse. Re-serializing the JSON and hashing that will not match — key order, spacing and number formatting all change the bytes.

Delivery is best-effort

There is no retry and no dead-letter queue. A POST that fails — a timeout, a connection error, any non-2xx — is logged and never re-sent. The delivery timeout is 5 seconds.Three consequences you have to design around:
  1. Answer immediately and do the work afterwards. Acknowledge with a 200 as soon as the signature checks out, then process from a queue of your own. A handler that writes to your database before replying will eventually take longer than five seconds and lose the event.
  2. Reconcile from the API, not from delivery. Treat webhooks as a latency optimization over GET /v1/calls, never as the record. A periodic sweep over calls in a window is what makes a missed delivery a non-event.
  3. Deduplicate on X-Talqing-Delivery. Your endpoint answering slowly and the event arriving anyway is a normal outcome.
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.
Two things make it different from a production event:
  • It bypasses the subscription filter. webhook.test is a probe of the endpoint, not a type anyone subscribes to, so it arrives whatever subscribed_events says.
  • A disabled webhook is refused with a 400: webhook is disabled; enable it before testing. A disabled endpoint must not receive traffic.
The response is { "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.
Rotation is immediate and there is no overlap window. The old secret stops signing anything the moment the call returns, so every event delivered from that instant is signed with the new one. Plan the cutover: have your receiver ready to accept both, rotate, then drop the old one — or accept a window in which your verification fails.

What a URL must be

The URL is checked when you configure it and again at every delivery.
  • The scheme must be http or https. Anything else is a 400: 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 400 reading url 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.
A destination that passes at configure time and fails at delivery — because DNS now answers with a private address — is logged in the delivery log as 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.