12 · webhooks

Webhooks: receive and send events

Two directions: your systems trigger the agent (inbound), and the platform notifies you of handovers and conversation endings (outbound). In both cases each message is signed with HMAC-SHA256 over the raw body.

  • Inbound: POST /api/v1/webhooks/{tenantId}/{sourceId}. Create a source (a secret is generated and shown once), then a trigger: event type → bot → channel (WhatsApp or SMS) → message template.
  • Headers: X-Webhook-Signature: sha256=<hex> (or X-Hub-Signature-256), X-Webhook-Timestamp (anti-replay, 300 s tolerance by default), X-Webhook-Id (idempotency).
  • The event type is read from the type, event or event_type field of the JSON. Response 202 { ok, matched }; any invalid signature returns 401 with no detail.
  • Outbound: Settings → Webhooks. Events: handoff.opened, handoff.taken, handoff.closed, conversation.closed.
  • Each outbound call is a JSON POST { event, at, … } with the X-Recla-Event header and, if a secret is set, X-Recla-Signature: sha256=<hex>. 8 s timeout, 2xx expected; the last status is shown in the interface.
  • The "Test" button sends a webhook.test event. URL must be http or https, with no embedded credentials.
Inbound webhook — sign and send (cURL)
BODY='{"type":"order.shipped","id":"evt_42","msisdn":"+2250700000000","order":"CMD-2291"}'
SECRET="secret-de-la-source"
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')

curl -X POST https://api.votredomaine.com/api/v1/webhooks/TENANT_ID/SOURCE_ID \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: sha256=$SIG" \
  -H "X-Webhook-Timestamp: $(date +%s)" \
  -H "X-Webhook-Id: evt_42" \
  -d "$BODY"
Outbound webhook — verify the signature (Node.js)
import crypto from "node:crypto";
import express from "express";

const app = express();
// Il faut le corps BRUT : la signature porte sur les octets reçus.
app.post("/recla-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto.createHmac("sha256", process.env.WEBHOOK_SECRET).update(req.body).digest("hex");
  const received = req.get("X-Recla-Signature") ?? "";
  const ok = received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);

  const { event, at, ...data } = JSON.parse(req.body.toString());
  console.log(req.get("X-Recla-Event"), event, at, data);
  res.sendStatus(200); // 2xx attendu sous 8 secondes
});
app.listen(3000);
Deep Agent StudioUse cases© 2026 Deep Agent Studio