Webhooks
Push every event Leadiosa generates to your own backend, signed with HMAC-SHA256 so you can verify provenance and integrity.
Setup
- 1Open
Settings → Integrations → Webhooksand click Add endpoint. - 2Paste the public HTTPS URL where Leadiosa should POST events.
- 3Pick which events you want to receive — default is everything.
- 4Copy the signing secret. This is your only chance to see it; store it in your secret manager immediately.
- 5Save. We send a test ping to verify reachability.
cloudflared or ngrok to tunnel.Payload format
{
"id": "evt_19f3a7c8",
"type": "message.created",
"created_at": "2026-05-12T07:28:25.901Z",
"workspace_id": "ws_8f3c12",
"data": {
"conversation_id": "conv_a91b",
"message_id": "msg_44f7",
"from": "visitor",
"text": "Hi, the webhook signature isn't matching...",
"attachments": []
}
}Every event has the same envelope: id (idempotency key), type, created_at, workspace_id, and a data object whose shape depends on the type.
Event catalogue
| Event type | Fires when |
|---|---|
conversation.created | A visitor opens a new conversation. |
conversation.assigned | An operator is assigned to a conversation. |
conversation.resolved | A conversation is marked resolved (closed). |
conversation.reopened | A resolved conversation receives a new message. |
message.created | Any message is sent — visitor, operator, or AI. |
contact.created | A new contact record is created. |
contact.identified | A previously anonymous contact is identified via leadiosa.identify. |
contact.erased | A contact is erased under GDPR. |
ai.handoff | The AI agent crosses the handoff threshold and bails to a human. |
operator.online | An operator signs in or comes back online. |
operator.offline | An operator signs out or goes away. |
Signature verification
Every delivery carries an X-Leadiosa-Signature header — the HMAC-SHA256 of the raw request body, computed with your signing secret.
You must verify the signature before trusting any payload. Without verification, anyone who guesses your URL can forge events.
import crypto from "crypto";
import express from "express";
const app = express();
// IMPORTANT: use express.raw, not express.json — we need the
// untouched bytes for HMAC verification.
app.post(
"/leadiosa-webhook",
express.raw({ type: "*/*" }),
(req, res) => {
const sig = req.header("X-Leadiosa-Signature");
const expected = crypto
.createHmac("sha256", process.env.LEADIOSA_WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
if (!sig || !crypto.timingSafeEqual(
Buffer.from(sig, "hex"),
Buffer.from(expected, "hex")
)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString());
// dispatch on event.type ...
return res.status(200).end();
}
);import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/leadiosa-webhook")
def webhook():
sig = request.headers.get("X-Leadiosa-Signature", "")
expected = hmac.new(
os.environ["LEADIOSA_WEBHOOK_SECRET"].encode(),
request.get_data(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json()
# dispatch on event["type"] ...
return ("", 200)Delivery and retries
- Each event is delivered at least once. Use the event id to deduplicate on your side.
- A 2xx response counts as success. Anything else triggers retry.
- Retries follow an exponential backoff: 1m, 5m, 25m, 2h, 12h, then 24h up to 72 hours total.
- After 72h of failed retries the event is dropped and the endpoint is flagged in the dashboard. Repeated failures across many events will disable the endpoint.
- Order is not guaranteed across event types. Within a single conversation, message order is preserved.
- We send no payload to disabled endpoints — re-enable from the dashboard once you’ve fixed the receiver.
Testing locally
For local development, tunnel your machine to a public HTTPS URL — the simplest way:
cloudflared tunnel --url http://localhost:3000
Paste the generated URL into the webhook config, hit the test button. Once you're happy, swap to your production URL and remove the tunnel.
See REST API for the inverse direction — pulling and pushing data on demand.