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 started
conversation.message_receivedCustomer message received on a channel
conversation.message_deliveredOutbound message confirmed delivered
conversation.message_failedOutbound delivery failed (channel error)
agent.intent_detectedAtlas identified an intent from customer message
agent.action_pendingAgent action awaiting human approval
agent.action_approvedPending action approved by operator
agent.action_rejectedPending action rejected by operator
agent.handoffSession handed off to human operator
session.endedConversation session closed

Register 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 checkingevent_id before 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 OK even 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 }