Docs · Build

Webhooks

Push every event Leadiosa generates to your own backend, signed with HMAC-SHA256 so you can verify provenance and integrity.

Setup

  1. 1Open Settings → Integrations → Webhooks and click Add endpoint.
  2. 2Paste the public HTTPS URL where Leadiosa should POST events.
  3. 3Pick which events you want to receive — default is everything.
  4. 4Copy the signing secret. This is your only chance to see it; store it in your secret manager immediately.
  5. 5Save. We send a test ping to verify reachability.
Watch out
Endpoints must be publicly reachable HTTPS — we do not deliver to private IPs, localhost, or self-signed TLS. For local development, use cloudflared or ngrok to tunnel.

Payload format

POST /your-endpointjson
{
  "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 typeFires when
conversation.createdA visitor opens a new conversation.
conversation.assignedAn operator is assigned to a conversation.
conversation.resolvedA conversation is marked resolved (closed).
conversation.reopenedA resolved conversation receives a new message.
message.createdAny message is sent — visitor, operator, or AI.
contact.createdA new contact record is created.
contact.identifiedA previously anonymous contact is identified via leadiosa.identify.
contact.erasedA contact is erased under GDPR.
ai.handoffThe AI agent crosses the handoff threshold and bails to a human.
operator.onlineAn operator signs in or comes back online.
operator.offlineAn 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.

Node.js / Expressjavascript
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();
  }
);
Python / Flaskpython
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:

bash
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.