Guide to Agntix Webhooks: Features and Best Practices
Agntix runs AI agents that answer and place phone calls, and hold chat conversations on the web and WhatsApp. Its webhooks tell your systems when one of those sessions starts and when it ends, and the end event carries the post-call analysis: a summary, plus intent, resolution, sentiment and extracted data if you have switched those on for the agent. Most integrations use them to write call outcomes back to a CRM or to start a follow-up task once a call closes.
What are Agntix webhooks?
Agntix delivers its webhooks through Svix. You add an endpoint in the dashboard, tick the events it should receive, and Agntix sends each one as a JSON POST with three Svix headers: svix-id, svix-timestamp and svix-signature. The signature follows the Standard Webhooks scheme, so any Svix or Standard Webhooks verifier can check it.
The event surface is small. Two events are delivered today, session.created and session.ended. Agntix's event reference describes several more, but labels them as planned and says they are not delivered as webhooks yet.
Agntix webhook features
| Feature | Details |
|---|---|
| Delivery platform | Svix |
| Configuration | Dashboard, Developer > + Add Endpoint |
| Signature | Standard Webhooks HMAC-SHA256 |
| Signature header | svix-signature, with svix-id and svix-timestamp |
| Secret | One per endpoint, in the Developer section |
| Body | JSON |
| Event routing | Body field eventType |
| Response window | Any 2xx within 15 seconds |
| Retries | At least once, exponential backoff for up to 24 hours, then dead-lettered |
| Ordering | Not guaranteed. Sort on createdAt |
| Rate limit | Optional requests-per-second cap per endpoint |
| Endpoints | Public HTTPS URLs only |
| Source IPs | Not published |
Events and payloads
| Event | Triggered When | Notable fields |
|---|---|---|
session.created | A session is initiated. For outbound calls this is before pickup | id, type, platform, direction, state (OPEN), agentId, createdAt, metadata |
session.ended | A session closes (hangup, timeout, TTL expiry or manual close) | the above with state CLOSED, plus lastActivityAt, endedAt, analysis, recordingUrl |
The body is flat, with no data wrapper. Three fields are easy to mix up:
eventTypeis the event name. Route on this.typeis the session kind,VOICEorCHAT. It is not the event name.idis the session UUID. Both events for one session carry the sameid, so a deduplication key needseventTypeas well.
The rest of the envelope describes the session: platform (PHONE, WEB or WHATSAPP), direction (INBOUND or OUTBOUND), state (OPEN, then CLOSED), agentId, createdAt and metadata. metadata.sessionVariables returns the session_variables you passed when you triggered the session, unchanged, which is how you tie a webhook back to your own record.
session.ended adds lastActivityAt, endedAt, recordingUrl and analysis. analysis.summary is always there. intent, resolution (RESOLVED, PARTIALLY_RESOLVED, UNRESOLVED or NOT_APPLICABLE), sentiment (POSITIVE, NEUTRAL or NEGATIVE, with a score from -1.0 to 1.0) and extractedData appear only when the matching feature is enabled on the agent.
This is the session.created sample from the webhook samples library:
{
"id": "4981804c-2658-4893-84ff-9c2fa90fb3c2",
"eventType": "session.created",
"type": "VOICE",
"platform": "PHONE",
"direction": "OUTBOUND",
"state": "OPEN",
"agentId": "40235145-aa3d-443e-9d0c-841dd0716a2f",
"createdAt": "2026-04-08T05:00:01.130Z",
"metadata": {
"customerPhoneNumber": "+971501234567",
"roomName": "outbound_sip_40235145-aa3d-443e-9d0c-841dd0716a2f^__^7yoamyb",
"sessionVariables": {
"crmContactId": "CRM-7728",
"customerName": "Alex Morgan"
}
}
}
For an outbound voice call, session.created fires before the callee picks up, so don't treat it as proof that anyone answered.
Setting up Agntix webhooks
- In the Agntix dashboard, open Developer (under Integrations) and click + Add Endpoint.
- Enter the Endpoint URL, a Description, and optionally a Rate Limit in requests per second. Set it comfortably above your peak call volume.
- Under Subscribe to events, tick
session.created,session.endedor both, and save. - Copy the endpoint's signing secret from the Developer section and store it as
AGNTIX_WEBHOOK_SECRET.
To correlate events with your CRM, pass your own identifiers in session_variables when you trigger a call or chat:
"session_variables": { "crmContactId": "CRM-7728", "campaignId": "CAMP-2026-05-13-001", "taskId": "TASK-9911" }
Agntix echoes those keys under metadata.sessionVariables on both events, whether or not the agent's prompt uses them.
To route Agntix through Hookdeck, create a source with the Agntix type, paste the endpoint's signing secret, and use the source URL as the endpoint URL in Agntix. During development, the Hookdeck CLI forwards those deliveries to a local port:
npx hookdeck-cli listen 3000 agntix --path /webhooks/agntix
Securing Agntix webhooks
Agntix's own sample verifies with the svix package. That works for a direct receiver, but the Svix library rejects any svix-timestamp more than five minutes from the current time. Hookdeck keeps the original headers when it retries or replays a request and applies no timestamp tolerance of its own, so a delivery held during an outage on your side would fail the library check. The verifier below checks the HMAC without an age limit.
It builds <svix-id>.<svix-timestamp>.<raw body>, keys an HMAC-SHA256 with the base64 bytes after whsec_, and accepts any v1, entry in the space-separated svix-signature header. If svix-id is missing it reads the webhook-* names instead, as the Hookdeck source does.
const crypto = require('node:crypto');
function decodeBase64(value) {
if (typeof value !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(value)) return null;
const bytes = Buffer.from(value, 'base64');
return bytes.length > 0 && bytes.toString('base64').replace(/=+$/, '') === value.replace(/=+$/, '')
? bytes : null;
}
function verifySignature(rawBody, headers, secret) {
if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
const prefix = headers['svix-id'] ? 'svix' : 'webhook';
const id = headers[`${prefix}-id`];
const timestamp = headers[`${prefix}-timestamp`];
const signature = headers[`${prefix}-signature`];
if (typeof id !== 'string' || !id || typeof timestamp !== 'string' ||
!/^[0-9]+$/.test(timestamp) || typeof signature !== 'string') return false;
const key = decodeBase64(secret.replace(/^whsec_/, ''));
if (!key) return false;
const message = Buffer.concat([Buffer.from(`${id}.${timestamp}.`, 'utf8'), rawBody]);
const candidates = signature.split(/\s+/).filter((item) => item.startsWith('v1,')).map((item) => item.slice(3));
const expected = crypto.createHmac('sha256', key).update(message).digest();
return candidates.some((candidate) => {
const received = decodeBase64(candidate);
if (!received) return false;
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
A receiver for Agntix stores the verified event under a key built from the session ID and the event name, then acknowledges well inside the 15-second window:
const express = require('express');
// Returns the parsed body when it is a JSON object, otherwise null.
function parseJsonObject(rawBody) {
try {
const value = JSON.parse(rawBody.toString('utf8'));
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
} catch {
return null;
}
}
// saveEvent(key, event) writes to your own durable store and resolves once it is stored.
function createAgntixReceiver(saveEvent) {
const app = express();
app.post('/webhooks/agntix', express.raw({ type: '*/*' }), async (req, res) => {
const secret = process.env.AGNTIX_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500);
if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);
const event = parseJsonObject(req.body);
if (!event) return res.sendStatus(400); // signed, but not a JSON object
if (typeof event.eventType !== 'string') return res.sendStatus(400);
// Both events share the session id, so the key needs the event name too.
const eventKey = `${event.id}:${event.eventType}`;
try {
await saveEvent(eventKey, event);
} catch {
return res.sendStatus(503); // Agntix retries with backoff
}
return res.sendStatus(200);
});
return app;
}
A worker then reads session.ended records and writes analysis and metadata.sessionVariables back to the CRM. If a request reaches a Hookdeck Agntix source with a bad signature, Hookdeck still answers 200 and marks it as failed verification rather than returning the 401 shown above.
Make Agntix webhooks production-ready. Hookdeck Event Gateway checks the Svix signature on every Agntix session event, keeps a searchable history of each call outcome, and retries delivery to your CRM sync on your schedule.
Agntix webhook limitations and pain points
Only two events are live
The Problem: Handlers written against session.updated, message.added or usage.updated never fire.
Why It Happens: Agntix documents those events so customers can design for them, but marks them as planned and "not delivered as webhooks yet". session.updated and knowledge_store.updated exist only as in-app real-time signals.
Workarounds:
- Subscribe to
session.createdandsession.endedonly, and acknowledge any othereventTypewith200so new events don't cause failures when they ship. - Poll the API for mid-session state if you need it now.
How Hookdeck Can Help: A Hookdeck filter on eventType can send the two live events to your service and hold anything new for inspection until you are ready for it.
The payload is a summary, not a transcript
The Problem: You need what was said on the call, and the webhook doesn't have it.
Why It Happens: Agntix describes the webhook as "summary-grade, not transcript-grade". The message-by-message transcript is left out, and some fields can be truncated on very long calls.
Workarounds:
- Treat
session.endedas the trigger and fetch the transcript fromGET /api/v1/chat/sessions/{id}/messages.
How Hookdeck Can Help: Hookdeck's transformations can trim or reshape the analysis before it reaches each destination, but the transcript itself still has to come from the API.
Events arrive out of order
The Problem: A session.ended can reach you before the session.created for the same call, especially when many calls run in parallel.
Why It Happens: Agntix states that order is not guaranteed, and a retried session.created can land after the session has already closed.
Workarounds:
- Upsert on the session
idand let theCLOSEDstate win. - Sort on
createdAtwhen you need a timeline.
How Hookdeck Can Help: Hookdeck records each delivery with its arrival time, so you can see the order events actually reached you and replay them while you debug a sync.
The signing secret's format isn't documented
The Problem: A verifier that expects whsec_ rejects every request, and you can't tell from the docs whether the secret is wrong.
Why It Happens: Agntix never shows the secret's form. Svix-issued secrets start with whsec_, and the Svix library and Hookdeck's source both base64-decode the part after that prefix, but Agntix doesn't confirm it.
Workarounds:
- Check the secret in the Developer section before you deploy. If it doesn't start with
whsec_, ask Agntix support how to use it.
How Hookdeck Can Help: Hookdeck's request log shows the exact headers and body of a failed verification, so you can tell a secret mismatch from a body that was changed in transit.
Best practices
Correlate through session_variables
Put a CRM contact ID or task ID in session_variables every time you trigger a session. Calls from one campaign look alike otherwise, and metadata.sessionVariables is the only field that links a webhook to your record.
Key deduplication on session and event
Agntix delivers at least once and recommends the session id as the idempotency key. Because session.created and session.ended share that ID, store id plus eventType. The webhook idempotency guide explains the pattern.
Check for optional analysis fields
Only analysis.summary is guaranteed. Read intent, resolution, sentiment and extractedData with optional access, because switching a feature off on the agent removes the field from later calls.
Size the endpoint rate limit
The Rate Limit setting caps requests per second per endpoint. Agntix suggests setting it comfortably above peak, for example 50 for bursts of 10 concurrent calls, so the cap never becomes the reason events arrive late.
Conclusion
Agntix sends one event when a session opens and another, carrying the analysis, when it closes. Correlate the two through session_variables, and treat the planned events as unavailable until they're live.
Hookdeck Event Gateway can sit between Agntix and your CRM sync, verifying the Svix signature and holding call outcomes while your service is down. Create a free Hookdeck account to try it with an Agntix endpoint.