Register an HTTPS endpoint in Developers (or with the API) and we'll POST events to it as they happen. Respond with any 2xx within 10 seconds. Otherwise we retry after 1 minute, 5 minutes, 30 minutes, 2, 6, 12 and 24 hours, then give up. Delivery is at-least-once, so use the event id to ignore duplicates.
json
{
"id": "evt_3f2a9c…",
"type": "message.received",
"schema_version": "2026-09-28",
"created_at": "2026-09-28T10:04:40.512Z",
"workspace_id": "ws_EWhJ…",
"data": {
"message": {
"object": "message",
"id": "m_77c0…",
"conversation_id": "v_3c20…",
"direction": "inbound",
"sender": "customer",
"type": "text",
"text": "Where is my order?"
}
}
}
| contact.created | A contact was created (first inbound message, API or dashboard) |
| conversation.opened | A new conversation started |
| message.received | A customer sent a message |
| message.sent | A message was sent to a customer (dashboard or API) |
| message.delivery_updated | A sent message changed status: sent, delivered, read or failed |
Verifying signatures
Every delivery has a Womnibot-Signature header like t=1790590000,v1=5257a8…. Compute an HMAC-SHA256 of {t}.{raw request body} using your endpoint's signing secret, compare it to v1 in constant time, and reject timestamps older than 5 minutes. Always verify against the raw body, before parsing JSON.
javascript (Node.js / Express)
import crypto from "node:crypto";
app.post("/webhooks/womnibot", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Womnibot-Signature") ?? "";
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto
.createHmac("sha256", process.env.WOMNIBOT_WEBHOOK_SECRET)
.update(`${t}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const valid = v1 && v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
if (!fresh || !valid) return res.sendStatus(400);
const event = JSON.parse(req.body);
// Deduplicate on event.id, then handle event.type…
res.sendStatus(200);
});
python (Flask)
import hmac, hashlib, time, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/webhooks/womnibot")
def womnibot():
parts = dict(p.split("=", 1) for p in request.headers.get("Womnibot-Signature", "").split(","))
raw = request.get_data()
expected = hmac.new(os.environ["WOMNIBOT_WEBHOOK_SECRET"].encode(),
f"{parts.get('t')}.".encode() + raw, hashlib.sha256).hexdigest()
if abs(time.time() - int(parts.get("t", 0))) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
abort(400)
event = request.get_json()
# Deduplicate on event["id"], then handle event["type"]…
return "", 200