Guide
Webhooks
External sales AI agents consume inbound Atlas events, fetch context and knowledge, then submit replies or actions back to Mirai for delivery over business channels. Webhooks replace polling for production integrations. Verify signatures, keep handlers idempotent, and preserve correlation IDs in logs.
Core event flow
When a customer sends a message on WhatsApp, Atlas receives it, runs intent detection, grounds the response in your knowledge corpus, and fires a conversation.message_receivedwebhook to your registered endpoint. Your agent processes it and calls POST /v1/atlas/actionsto reply or handoff. Atlas then delivers the outbound message through the WhatsApp Business API.
Atlas → POST /your-server/events (webhook)
{
"event_id": "evt_01HXAB",
"event_type": "conversation.message_received",
"conversation_id": "conv_01HX9Z",
"channel": "whatsapp",
"customer": { "phone_number": "+6590000000" },
"message": { "id": "wamid.xxx", "type": "text", "text": "Is the menu available?" },
"timestamp": "2026-05-21T10:00:00Z"
}
Your agent → GET /v1/atlas/context/{conversation_id}
Your agent → GET /v1/atlas/knowledge?q=menu
Your agent → POST /v1/atlas/actions {
"idempotency_key": "act_01HXAC",
"action": "send_message",
"conversation_id": "conv_01HX9Z",
"message": { "type": "text", "text": "Here is our menu: ..." }
}Available events
conversation.createdNew inbound conversation thread startedconversation.message_receivedCustomer message received on a channelconversation.message_deliveredOutbound message confirmed deliveredconversation.message_failedOutbound delivery failed (channel error)agent.intent_detectedAtlas identified an intent from customer messageagent.action_pendingAgent action awaiting human approvalagent.action_approvedPending action approved by operatoragent.action_rejectedPending action rejected by operatoragent.handoffSession handed off to human operatorsession.endedConversation session closedRegister a webhook
Register your endpoint and select the events you want to receive. Each webhook gets a unique signing secret used to verify payload authenticity.
POST /v1/atlas/webhooks
{
"url": "https://your-server.com/atlas/events",
"events": [
"conversation.message_received",
"conversation.message_delivered",
"agent.handoff"
],
"secret": "whsec_your_signing_secret",
"description": "Production inbound handler"
}
Response: {
"webhook_id": "wh_01HX9Z",
"url": "https://your-server.com/atlas/events",
"events": ["conversation.message_received", ...],
"status": "active",
"created_at": "2026-05-21T10:00:00Z"
}Verifying signatures
Every webhook request includes an Atlas-Signature header — an HMAC-SHA256 of the raw request body using your webhook secret. Reject any request where the signature does not match. Never re-compute the signature from a parsed JSON body; use the raw bytes.
import crypto from "crypto";
function verifyWebhookSignature(
rawBody: Buffer,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express handler example
app.post("/atlas/events", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["atlas-signature"] as string;
if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
// process event...
res.json({ received: true });
});Retry and delivery semantics
- Atlas delivers webhooks at-least-once. Your endpoint must handle duplicate events by checking
event_idbefore processing. - If your endpoint returns a non-2xx response, Atlas retries with exponential backoff over 24 hours (up to 5 retry attempts).
- Set your handler to respond in under 5 seconds. For long-running processing, acknowledge immediately and queue the event asynchronously.
- Return
200 OKeven for events you intentionally skip; only return non-2xx to trigger a retry.
List and manage webhooks
GET /v1/atlas/webhooks
Response: { "data": [{ "webhook_id": "wh_01HX9Z", "url": "...", "status": "active" }] }
DELETE /v1/atlas/webhooks/{id}
Response: { "status": "deleted" }
PATCH /v1/atlas/webhooks/{id}
Body: { "events": ["conversation.message_received"], "url": "https://new-endpoint.com/events" }
Response: { "webhook_id": "wh_01HX9Z", ...updated }