Webhooks
Webhooks push events to your backend as they happen, so you do not have to poll.
Subscribing
Register an endpoint and pick the events you want. Subscriptions are validated against
the catalog below, so a typo cannot silently swallow deliveries. GET /webhooks/events
returns the same catalog.
An endpoint can also carry:
timeout_seconds- how long to wait for your response, from 1 to 300. The default is 10 seconds.headers- extra headers sent with every delivery.auth_header- sent as theAuthorizationheader, for a receiver that expects one. It is write-only: reads report only whether one is set.
Custom headers cannot replace the signature, event or delivery headers below.
Events
| Event | When |
|---|---|
session.started | A session or call has started. |
session.completed | A session or call finished. Carries the end-of-call report: transcript, variables, cost, latency. |
transcript.updated | A new turn was added to a live session. |
function.called | The model invoked a tool during a live call. |
voicemail.detected | An outbound call Glytos placed was answered by a machine, as reported by the carrier's answering-machine detection. |
call.recording.ready | A recording finished processing. Carries the session uuid; fetch the audio from the session with your API key. |
sip.trunk.registered | A SIP trunk registered with its carrier and can take calls. |
sip.trunk.down | A SIP trunk lost its registration. Calls to its numbers will not arrive. |
workflow.published | An agent version was published. |
workflow.promoted | An agent was promoted to another environment. |
member.invited | A member was invited to the organization. |
session.completed is the one most integrations need: it is the whole conversation
and its outcome in one delivery. It carries no recording address; see
Sessions.
What a delivery looks like
Every delivery is a JSON POST. The body names the event and carries its payload
under data:
{
"event": "call.recording.ready",
"data": { "session_uuid": "..." }
}It comes with these headers:
| Header | What it is |
|---|---|
X-Glytos-Signature | The signature, described below. |
X-Glytos-Event | The event type, the same as event in the body. |
X-Glytos-Delivery | The delivery id. A redelivery sends the same id again. |
Verifying a delivery
Every delivery is signed. The signature is an HMAC-SHA256 over the timestamp and the raw body, sent as:
X-Glytos-Signature: t=<timestamp>,v1=<signature>Verify it before trusting a payload:
import { verifyWebhook } from '@glytos/node';
// `rawBody` must be the untouched request body.
const ok = verifyWebhook(rawBody, req.headers['x-glytos-signature'], secret);from glytos import verify_webhook
ok = verify_webhook(raw_body, request.headers["X-Glytos-Signature"], secret)use Glytos\Webhook;
$ok = Webhook::verify($rawBody, $_SERVER['HTTP_X_GLYTOS_SIGNATURE'], $secret);ok := glytos.VerifyWebhook(rawBody, r.Header.Get("X-Glytos-Signature"), secret,
glytos.DefaultWebhookTolerance)using Glytos;
var ok = Webhook.Verify(rawBody, Request.Headers["X-Glytos-Signature"], secret);# No Ruby SDK: parse "t=<timestamp>,v1=<signature>" from the header yourself.
parts = header.split(',').map { |p| p.split('=', 2) }.to_h
expected = OpenSSL::HMAC.hexdigest('SHA256', secret, "#{parts['t']}.#{raw_body}")
ok = Rack::Utils.secure_compare(expected, parts['v1'].to_s)Every server SDK ships this check - Node, Python, PHP, Go and .NET - so you do not have to implement it; see the SDKs. Each also rejects a delivery whose timestamp is more than five minutes old, which you can change through its tolerance argument. In any other language it is the few lines above.
Verify against the raw request body, before any JSON parsing. Parsing and re-serialising changes the bytes and the signature will never match.
Reject deliveries whose timestamp is old; the SDK helpers do this by default. If you verify by hand, add the check yourself. Without it a captured, valid delivery can be replayed at you later.
Expectations
Return a 2xx quickly and do the work afterwards. A handler that blocks on slow work is a handler that times out.
A delivery is attempted once. A non-2xx response, a timeout or an unreachable endpoint
marks it failed, and it is not retried automatically. List deliveries with
GET /webhooks/deliveries (filter by status and event_type) and resend one with
POST /webhooks/deliveries/{delivery_id}/redeliver. If missing an event matters, check
for failed deliveries rather than assuming every event arrived.
A redelivery can reach you after the original did, so make handling an event
idempotent - key on the session uuid or on X-Glytos-Delivery rather than assuming
exactly-once.