Webhooks let Baat notify your systems the instant something happens — a contact is created, a conversation opens or closes, or a customer submits a WhatsApp Flow form. Instead of polling the API, you register a URL and Baat POSTs the event to it in real time. Manage them under Developers → Webhooks.
Creating a webhook #
Click Add webhook and provide:
- Name — e.g. CRM contact sync.
- Endpoint URL — the HTTPS URL Baat will POST to, e.g.
https://yourapp.com/webhooks/baat. - Event — which event this endpoint subscribes to (one event per webhook).
- Custom auth header (optional) — a header name/value pair Baat adds to each delivery, so your endpoint can authenticate the caller (e.g.
Authorization: Bearer …).
Available events #
| Event | Fires when… |
|---|---|
contact.created |
A new contact is added to your organization. |
contact.updated |
A contact’s tags or details change. |
conversation.opened |
A conversation is opened or re-opened. |
conversation.closed |
A conversation is closed. |
flow_response.submitted |
A customer submits a WhatsApp Flow form. |
Payload format #
Baat sends a JSON POST with Content-Type: application/json. The body looks like:
{
"event": "contact.created",
"occurred_at": "2026-05-31T09:15:42Z",
"organization_id": 4567,
"data": {
"...": "the full entity that triggered the event"
}
}
| Field | Description |
|---|---|
event |
The event key, e.g. contact.created. |
occurred_at |
ISO-8601 UTC timestamp of when the event happened. |
organization_id |
Your Baat organization ID. |
data |
The entity snapshot (the contact, conversation or flow response). |
Verifying the signature #
Every delivery carries an X-Baat-Signature header so you can confirm the request is authentic and untampered. The value is an HMAC-SHA256 of the raw request body, keyed with your signing secret, hex-encoded and prefixed with sha256=:
X-Baat-Signature: sha256=4f1e2d…
To verify, compute the same HMAC over the raw body and compare. Node.js example:
const crypto = require("crypto");
// Use the raw, unparsed request body (e.g. express.raw())
function verifyBaatSignature(rawBody, headerValue, signingSecret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", signingSecret)
.update(rawBody, "utf8")
.digest("hex");
// constant-time compare
const a = Buffer.from(headerValue || "");
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhooks/baat", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.header("X-Baat-Signature");
if (!verifyBaatSignature(req.body, sig, process.env.BAAT_WEBHOOK_SECRET)) {
return res.status(401).send("bad signature");
}
const event = JSON.parse(req.body.toString("utf8"));
// …handle event…
res.sendStatus(200);
});
Responding & retries #
Return a 2xx status quickly to acknowledge receipt. If your endpoint errors or times out, Baat retries with an increasing backoff — up to 8 attempts:
| Attempt | Roughly after |
|---|---|
| 1 | immediately |
| 2 | 10 minutes |
| 3 | 1 hour |
| 4 | 6 hours |
| 5 | 12 hours |
| 6 | 24 hours |
| 7 | 4 days |
| 8 | 7 days |
After the final failed attempt the delivery is marked Failed. Because an event can be delivered more than once, make your handler idempotent (de-duplicate on the entity ID plus occurred_at).
Inspecting deliveries — Logs & usage #
The Logs & usage tab records every delivery attempt so you can debug integrations without adding logging on your side.
| Column | Meaning |
|---|---|
| Event | Which event fired. |
| Endpoint | The URL Baat called. |
| Status | Success, Failed, or Pending (awaiting a retry). |
| Attempts | How many delivery attempts have been made. |
| Error | The last error returned by your endpoint, if any. |
| Fired at | When the most recent attempt was made. |
Enabling, disabling & deleting #
Use the ⋮ menu on a webhook row to Disable it (Baat stops sending events but keeps the configuration and secret) or Delete it permanently. Re-enable a disabled webhook from the same menu.