Skip to content
AnytimeReply

Get started

Webhooks

Events we send, the payload of each, delivery and retries, and how to verify the signature in Node.js, PHP and Python.

Webhooks send a POST request with JSON to your URL when something happens — for Zapier, Make, n8n, your CRM or your own server.

Add a webhook

Open your agent's Integrations page and pick Custom webhook (or Zapier, Make, n8n, Google Sheets, HubSpot — these use the same webhook under the hood). Paste the URL, choose the events under Send when… and save. Each agent can have up to 20 integrations.

  • Show signing secret reveals the secret (whsec_…) used to sign requests.
  • Send test posts a sample of the first selected event right away (with "test": true) and shows what your server answered — Zapier needs this to see the fields.

Events

EventSent when
conversation.createdA new conversation starts (website, messaging apps, email, API)
lead.createdThe agent saves a lead, a form is submitted, a flow saves a lead, or MCP create_lead runs
handoff.requestedA chat is handed to your team
conversation.closedA conversation is closed by your team or closed automatically
csat.receivedThe customer rates the chat

Tests never send webhooks.

Request

HTTP
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: AnytimeReply-Webhook/1.0
X-AnytimeReply-Event: lead.created
X-AnytimeReply-Signature: sha256=5b2c…e91f

Every body has the same envelope:

JSON
{
  "event": "lead.created",
  "agent": { "id": "AGENT_ID", "name": "Support" },
  "created_at": "2026-10-19T09:30:00.000Z",
  "data": { "...": "event-specific, see below" }
}

When the event belongs to a conversation, data.link is the chat in your dashboard.

Payloads

conversation.created

JSON
{
  "conversation_id": "c1a2…",
  "channel": "WHATSAPP",
  "contact": { "name": "Nadia", "phone": "+8801711111111" },
  "link": "https://anytimereply.com/dashboard/agents/AGENT_ID/inbox?c=c1a2…"
}

For the website chat, contact is replaced by page_url (the page the visitor started on). For the API, contact has name and email.

lead.created

JSON
{
  "conversation_id": "c1a2…",
  "lead": {
    "name": "Rahim",
    "email": "[email protected]",
    "phone": "+8801700000000",
    "form": "Call me back",
    "data": { "name": "Rahim", "phone": "+8801700000000", "order_id": "A-1001" }
  },
  "link": "https://anytimereply.com/dashboard/agents/AGENT_ID/inbox?c=c1a2…"
}

form and data (every submitted field) are present for form submissions. Leads the AI saves have notes; flow leads have "source": "flow". MCP leads have conversation_id: null.

handoff.requested

JSON
{
  "conversation_id": "c1a2…",
  "channel": "WIDGET",
  "reason": "Customer asked for a person",
  "mood": "frustrated",
  "summary": "Paid for the Pro plan but the account is still on Free.",
  "contact": { "name": "Nadia", "phone": null },
  "link": "https://anytimereply.com/dashboard/agents/AGENT_ID/inbox?c=c1a2…"
}

conversation.closed

JSON
{
  "conversation_id": "c1a2…",
  "channel": "TELEGRAM",
  "contact": { "name": "Nadia", "phone": null, "email": null },
  "auto": true,
  "link": "https://anytimereply.com/dashboard/agents/AGENT_ID/inbox?c=c1a2…"
}

auto: true appears when the chat was closed automatically after your team answered.

csat.received

JSON
{
  "conversation_id": "c1a2…",
  "rating": 5,
  "comment": "Super fast, thanks!",
  "link": "https://anytimereply.com/dashboard/agents/AGENT_ID/inbox?c=c1a2…"
}

rating is 1–5. Website ratings can include a comment; ratings from messaging apps include contact instead.

Verify the signature

X-AnytimeReply-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request body, using your webhook's signing secret. Compute it over the exact bytes you received (before parsing JSON) and compare in constant time.

const crypto = require("crypto");
const express = require("express");
const app = express();

// keep the raw body for the signature check
app.post("/webhooks/anytimereply", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto.createHmac("sha256", process.env.ANYTIMEREPLY_WEBHOOK_SECRET).update(req.body).digest("hex");
  const got = req.get("X-AnytimeReply-Signature") || "";
  const ok = got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
  if (!ok) return res.status(401).send("bad signature");

  const event = JSON.parse(req.body.toString("utf8"));
  if (event.event === "lead.created") {
    // save event.data.lead …
  }
  res.sendStatus(200);
});

Delivery and retries

  • Reply with any 2xx status within 10 seconds. Anything else counts as a failure.
  • Failed deliveries are retried: 3 attempts in total, about 15 and 30 seconds apart.
  • Deliveries happen in the background and never slow down the chat. Order isn't guaranteed — use created_at and conversation_id.
  • The last status and error of each webhook are shown on the Integrations page.
  • Your URL must be publicly reachable over HTTP(S); private network addresses are refused.

Respond quickly with 200 and do slow work (CRM calls, emails) in a background job. Use conversation_id to ignore duplicates if a retry arrives after a slow success.

Slack alerts

Pick Slack on the Integrations page and paste a Slack incoming webhook URL (Slack: create an app → Incoming Webhooks → Add New Webhook to Workspace → pick a channel). The same events arrive in that channel as short, readable messages with an Open chat link — no signature needed. To let the agent answer in Slack, see Slack channel.

Trigger webhooks

Triggers have their own Call a webhook action with a different payload (the whole conversation context) and an optional secret, sent as a plain hex signature without the sha256= prefix.

Was this page helpful?
Still stuck? Contact us
Webhooks – API reference · AnytimeReply