Webhooks
Webhooks push events from Yedric to a URL you control, as they happen. Every request is signed so you can verify it came from us, failed deliveries retry automatically, and a delivery log shows what was sent and what came back.
Webhooks can be configured on Organization > Integrations > Webhooks. See Integrations for that walkthrough. This page is the technical reference for the endpoint receiving them.
Event catalog
Eight events are subscribable, in two groups.
chat.started
A visitor requested a live chat and joined the queue.
chat.assigned
An agent took the chat, either by claiming it or via auto-assign.
chat.transferred
An agent handed an in-progress chat to a different agent.
chat.sentiment_set
The visitor rated the chat good or bad.
chat.ended
The chat closed. Says whether the visitor, an agent, a timeout, or a handoff back to the assistant ended it, and carries the agent notes on the chat.
conversation.created
A visitor's first message opened a new AI conversation.
conversation.escalated
The assistant handed the visitor off to a human.
conversation.summarized
An idle conversation was analyzed. Includes its summary, sentiment and topics.
The chat.* events describe live chat, so they can only occur for organizations that have it enabled. Nothing in the webhook layer checks for it: those chats simply cannot be created without it. The conversation.* events fire for everyone.
There is a ninth event name, webhook.test, which is not subscribable. It is only produced by the Send test event action, and it appears in the delivery log's event filter so you can find those rows.
The catalog is also readable at GET /api/webhooks/events if you would rather not hard-code it.
The request
Yedric sends a POST with a JSON body and these headers:
Content-Type
application/json
User-Agent
Yedric-Webhooks/1.0
X-Yedric-Event
The event name, for example chat.ended.
X-Yedric-Delivery
The delivery id. Stable across retries, so use it as an idempotency key.
X-Yedric-Webhook-Id
Which webhook this is, so you can pick the right signing secret.
X-Yedric-Timestamp
Unix seconds at the moment of sending, and part of the signed string.
X-Yedric-Signature
v1= followed by the hex HMAC.
Respond with any 2xx. Anything else counts as a failure.
The webhook id appears only in the header, never in the body. You need it to choose a signing secret, and that decision has to happen before the body can be trusted, so the header is the only place it can actually be used.
Note that X-Yedric-Timestamp is the send time, not the envelope's createdAt. The two differ on a retry, and it is the header that is signed.
Verifying the signature
The signature covers the timestamp and the raw body together:
Three things matter here:
The timestamp is inside the MAC. Signing the body alone would leave the timestamp forgeable, which makes rejecting replays pointless. Because it is signed, your freshness window is real.
Verify against the raw request bytes. Parsing the JSON and re-serializing it is not guaranteed to reproduce the same bytes, and both sides have to hash the same ones. In Express, capture
rawBodyvia theverifyoption onexpress.json().The
v1=prefix is a version scheme. It leaves room for comma-separated values during a secret rotation, so match on the value you expect rather than assuming a single one forever.
The signature legitimately differs between attempts of the same delivery, because the timestamp is fresh and the attempt counter in the body increments.
The payload
agentId is omitted rather than sent as null when an event has no assistant context, so type it as optional. The same is true of truncated, which is only ever present when it is true.
Every event carries assistantName in data, beside whatever else the event includes, so you can name the assistant a chat or conversation came from without calling back to the API. agentId in the envelope identifies the same assistant; the two go together. Like agentId, the key is omitted rather than sent as null when the assistant has no name or no longer exists, so type it as optional too.
The serialized envelope is capped at 256 KB. Over that, data is emptied entirely and truncated is set to true. It is not trimmed to fit. When you see truncated, fetch the record from the API instead of working with what arrived.
That cap is measured once, when the delivery is queued. Retries re-serialize the stored envelope with a new attempt number and are not re-measured, so a payload sitting right on the boundary can arrive a few bytes over it. Do not treat 256 KB as a hard guarantee when sizing your parser.
Retries and delivery guarantees
A failed delivery is tried up to six times in total, one first attempt plus five retries. The delay before each retry grows, and carries jitter of plus or minus 20 percent:
1
none
immediately
2
10s
10s
3
30s
40s
4
2m
2m 40s
5
10m
12m 40s
6
1h
1h 12m
Each request times out after 10 seconds. The first 2 KB of your response body is stored and shown in the delivery log, which makes debugging much easier if you return a useful error string.
404 and 410 are terminal. The route is gone, and that does not improve by waiting, so no retries are attempted. Three other conditions also end a delivery immediately rather than retrying: the webhook was deleted while the delivery was queued, its signing secret could not be decrypted, and the callback URL failed revalidation (see Callback URL requirements).
Delivery is at least once. If a worker dies after sending but before recording the outcome, the delivery is reclaimed and legitimately sent again. Deduplicate on X-Yedric-Delivery.
Repeated failure disables the webhook. After 20 consecutive deliveries have exhausted all their retries, the webhook is switched off and the dashboard shows "Auto-disabled after repeated failures." Note that the count is of fully dead deliveries, not individual attempts, so this takes sustained failure. A single success resets the streak. Test events never count toward it, and re-enabling the webhook resets the count.
Notes, attachments and redelivery
chat.ended is the one event that includes the agent notes written during the chat, as a notes array in data. Each note has an id, its text, a kind of note or closing (the closing note is the write-up an agent leaves when ending the chat), createdAt, and the author's display name as authorName, which is omitted when it is not known. Notes never identify the author by id.
Agent notes on chat.ended can carry attachments. Each one includes key, name, contentType, size, and a presigned url with a urlExpiresAt. The last two are omitted, not null, if presigning fails.
That URL is valid for 24 hours. It has to outlive the retry schedule, and it is an unauthenticated link to a customer's file, so it is deliberately short-lived.
The consequence to plan for: Redeliver reuses the stored payload. Redelivering a chat that is more than a day old hands your receiver an expired link. Download attachments promptly, and treat key as the durable handle rather than the URL.
Callback URL requirements
Enforced when the URL is saved and again on every delivery, including after each redirect.
Scheme must be
httporhttps. In production,httpsis required.The port must be the protocol default, or one of
80,443,8080,8443. Anything else is rejected when you save, with a message naming the allowed ports.The hostname must resolve to a public IP address. Private, loopback, link-local, and reserved ranges are refused.
Redirects are followed up to five hops, and every hop is re-validated. Only
accept,accept-language,content-typeanduser-agentsurvive a cross-origin redirect, so everyX-Yedric-*header including your signature is dropped. Terminate the request at the URL you configured rather than bouncing it.
Your receiver has to be publicly reachable
Because loopback and private ranges are refused, there is no way to point a webhook at a service running on your own machine, and localhost and 127.0.0.1 are both rejected. While you are building, put your receiver behind a tunnel (from a service like Ngrok) that gives you a public https URL and configure that. Send test event is the fastest way to confirm the tunnel works before you subscribe to anything real.
Last updated