Endpoints
Fields
string
required
Whose events. Required on create — unless
includeCustomers is true.boolean
For a workspace with customers: one endpoint for the events of every
agent in every one of your customers. It has no
agent (send one or the other, not both). Each
delivery’s data carries workspaceId — the customer it is about — inside the signed body.string
required
An
https:// address, or an email address — a helpdesk that opens tickets by
mail gets one email per event. Addresses inside our own network are refused.string[]
Which events to send. Leave it out for every event, now and as new ones are
added; the response then reads
["*"].boolean
Switching an endpoint back on forgives its past failures.
call.started is not a webhook event you subscribe to. It is the request your own system receives
from an agent’s Who-is-calling lookup, signed the same
way, which you answer with values for the agent to use.Problems on a live call
call.problem is sent while the call is still going on, so you can alert
somebody or flag the call before it ends. Each kind is sent at most once per
call, and a burst across many calls is held to about 20 events a minute per
agent. An event held back by that limit is not queued: it is sent if that kind
happens again on the same call once the agent is back under the limit, and
otherwise it is not sent at all. The same problems are also on the call itself,
under Problems on this call in Usage.
callId is the id Get Call takes. detail is
a sentence you can show a person.
Call quality
Every call the agent ran carriesquality, written once when the call ends —
on Get Call and in data.quality on
call.completed and call.analysed:
quality is null for a call that was not measured (a call from before these
figures were recorded, or one that never connected), and a figure inside it is
null when it could not be measured on that call. On call.completed it can
also be null when the call ended before its figures were ready;
call.analysed and Get Call have them by then. In the console the same
figures are under How the call went when you open the call in
Usage.
Calls handed to another agent
When an agent passes a live call to another of your agents (how),call.analysed and Get Call list it in handedTo,
oldest first — empty when the call stayed with agent, which is always the
agent the call was placed or answered as:
conversation.escalated.
Which agent an event names. The call-level events — call.completed,
call.analysed and call.problem — stay with the agent the call started
with, which is also the one the call is billed and summarised under. Events
the agents themselves raise mid-call — lead.captured,
conversation.escalated — name the agent that ran the tool in data.agentId
and carry data.callId, so on a handed call you can join them to the call
report and to handedTo.
Webhooks belong to an agent. An event raised by the agent a call was handed
to goes to that agent’s webhooks, not to the agent that took the call first.
If you only subscribe the first agent, you will get call.completed and
call.analysed for the whole call but miss any lead.captured or
conversation.escalated the second agent raises. Add the same endpoint to every
agent a call can be handed to.
Delivery
Each event is aPOST of JSON:
Retries
Anything other than a 2xx answer — an error, a timeout after 10 seconds, an address we cannot reach — is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours: six attempts over about 8½ hours. Every attempt sends the same bytes, sosentAt is when the event happened, not when the
attempt left. Payloads are kept for 7 days, so anything that still did not
arrive can be sent again within that week.
Re-sending a delivery starts its retry schedule afresh. Retries and re-sends
go to the endpoint’s current address — change the URL and pending retries
follow it. A Slack or helpdesk destination that accepted a post but answered
after the 10-second timeout can receive it twice, since neither can dedupe on
the event id.
What your receiver should do
- Verify
x-thinnest-signature-v2(orx-thinnest-signature) with a constant-time compare. - Check freshness on
x-thinnest-delivered-at— reject one more than five minutes from your clock. Not onsentAt: a retry’ssentAtis hours old by design. The delivery time is covered by the v2 signature, so it cannot be moved without breaking it. - Dedupe on
x-thinnest-event-id. A retry after a timeout can reach you after you already handled the first attempt. - Answer 2xx quickly, then do the work.
Switched off
An endpoint is switched off —enabled: false, delivery.lastError says why —
after five events in a row that each failed every attempt. A failed attempt
that a retry then delivers does not count. Events that happen while it is off
are recorded as not sent; fix the endpoint, PATCH { "enabled": true }, and
re-send them — nothing within the last 7 days is lost.
Event payloads
Every delivery is the same envelope. What changes isdata, shown here in full for each event.
Ids in data are the same prefixed ids the API returns and takes: conv_… for a conversation
(pass it to List Conversation Messages), cmp_… for a
campaign, and out_…, sch_… or the call reference for a call (pass it to
Get Call). conversationId and campaignId
always carry their conv_ and cmp_ prefix, so a value from a webhook can be
passed straight to the API. If your receiver stored these ids as bare
identifiers before, store the prefixed value from now on.
An endpoint created with includeCustomers: true also gets data.workspaceId (org_…), the
customer the event is about.
lead.captured
The agent collected contact details. name, email and phone are each null when not given; note is what the customer wants, written for whoever follows up. agentId is the agent that captured it and callId the call it was on (the id Get Call takes; null off a call).
conversation.escalated
The agent handed the conversation to a person. reason is the agent’s own words. agentId is the agent that handed over and callId the call it was on (null off a call).
conversation.resolved
A teammate marked the conversation done. Sent once, when the status actually changes.
call.completed
The moment a call ends, before it has been read. outcome is completed, missed or failed; hangup is answered, no_answer, busy, rejected, unreachable, cancelled, voicemail, before_agent or failed; phone is null on a web call. campaignId is present only for a campaign’s call, and id, reference and metadata only for a call placed through the API.
quality is how the call went in numbers (see Call quality); it
can be null when the call ended before its figures were ready. forwardedTo
appears only on a call your number forwarded to a phone, and is that phone’s
number; it is left out of every other call.
call.analysed
The call once it has been read: exactly the object Get Call returns. quality can be null (see Call quality), handedTo is empty unless the call was handed to another agent, and forwardedTo is null unless the call was forwarded to a phone.
campaign.finished
Everyone on a broadcast (kind: "whatsapp") or a calling campaign (kind: "voice") has been reached or given up on. skipped is people who withdrew consent before their turn.
contact.opted_out
During a call, somebody asked not to be called again and their number went on the do-not-call list. callId is the call’s id; source is always "call"; reason is what the caller said, as recorded (up to 160 characters, in the language they spoke), or null when the agent did not record their words.
call.problem
Something went wrong while a call was live. kind is no_answer_from_model, byok_failed, slow_reply or voice_failed; detail says what happened in a sentence; callId is the id Get Call takes. Sent at most once per kind per call.
Workspace events
contact.erased and byok.changed are about your whole workspace rather than one agent. They go to
every endpoint in the workspace that subscribed to them, whichever agent you added it on, and
once per address: if three agents post to the same URL, that URL still gets one delivery. If you
are a developer, an endpoint that receives every customer’s events (includeCustomers) gets them for
your customers too, with workspaceId. An endpoint subscribed to every event (["*"]) receives them
as well. Neither event carries personal details or any part of a key.
contact.erased
A contact you deleted with Delete Contact is gone completely, recordings
included. It is sent once: straight away when Delete Contact answered 204, or — when it answered
recordingsPending because a recording could not be deleted at that moment — from the nightly retry,
once the last of those recordings is gone. A recording that was still being written when you deleted
the contact can take up to three nights to be confirmed gone. contactId is the id you deleted.
byok.changed
Your own keys changed. change is one of:
kind (stt, llm or tts) and provider name the key for the four key changes, and are null
for the others. byokEnabled and scope say where your workspace stands after the change.
Choosing a different model for a key is not sent. Saved keys are not re-checked on a schedule: a model
key that stops working on a call shows up as call.problem (byok_failed).
Re-send what failed
sentAt as the first attempt, with
x-thinnest-attempt one higher. The endpoint must be switched on. The console’s
Re-send failed button on the endpoint does the same for the last 7 days. A
build key may do this.
Test an endpoint
lead.captured — marked "test": true, with obviously fake
details — the same way a real event goes: signed JSON, Slack’s own format for
a Slack URL, or a ticket email for an email address. A failed test counts
towards the five failures that switch an endpoint off.
Delivery history
status and error are from the latest
attempt; 0 means we could not reach it. nextRetryAt is when the next
automatic attempt is due — null once it arrived or ran out of retries. The
payloads themselves are not returned: they carry your customers’ details.