View Categories

Webhooks: real-time events

2 min read

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 …).

On creation Baat generates a signing secret and shows it once. Copy it into your secret store immediately — you’ll use it to verify that incoming requests genuinely came from Baat.

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);
});
Compute the HMAC over the exact bytes Baat sent. If your framework re-serializes the JSON before you hash it, whitespace or key-order differences will break the comparison — always hash the raw body.

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.