tufu
Start free
Docs menu

Human handoff

Webhooks

Send each new lead to Zapier, Make or your own server as it arrives, signed so you can check it came from tufu.

On this page

A webhook sends each new lead, and each change to one, to an address you choose, the moment it happens. Use it to put leads in your CRM, a spreadsheet or your email list, with Zapier, Make or your own code. Webhooks come with the Team plan.

Admins add them under Settings → Webhooks.

Events#

  • lead.created: a visitor leaves their details for the first time.
  • lead.updated: a lead changes: the visitor leaves their details again, a teammate edits them, or its status changes (New or Followed up).

Each is a POST with a JSON body like this (a conversation's words are never sent):

json
{
  "id": "evt_1f0c…",
  "type": "lead.created",
  "created": "2026-10-26T16:56:02+00:00",
  "test": false,
  "data": {
    "lead": {
      "id": 42,
      "email": "riley@example.com",
      "name": "Riley Chen",
      "phone": "+1 555 0142",
      "custom": {"planned_trip": "A week"},
      "custom_labels": {"planned_trip": "Planned trip"},
      "consent": {"asked": true, "given": true, "text": "Send me news and offers by email.", "answered_at": "2026-10-26T16:56:00+00:00"},
      "status": "new",
      "status_changed_at": null,
      "page_url": "https://example.com/pricing",
      "attribution": {"utm_source": "google", "utm_medium": "cpc", "utm_campaign": "fall-sale", "ref": "www.google.com"},
      "created_at": "2026-10-26T16:56:00+00:00",
      "updated_at": "2026-10-26T16:56:00+00:00"
    },
    "bot": {"id": 7, "name": "Help"},
    "conversation_url": "https://tufu.ai/a/your-team/inbox/c/…/",
    "team": {"slug": "your-team", "name": "Your team"}
  }
}

With Zapier#

  1. In Zapier, make a Zap whose trigger is Webhooks by Zapier → Catch Hook, and copy its address.
  2. In tufu, Settings → Webhooks → Add a webhook: paste the address and keep A visitor leaves their details.
  3. Press Send test. Zapier receives a sample lead, so you can map its fields (lead.email, lead.name, …) in the next step: HubSpot, Mailchimp, Google Sheets or anything else Zapier connects.

With Make#

  1. In Make, start a scenario with Webhooks → Custom webhook, and copy its address.
  2. Add it in tufu the same way, then press Send test: Make learns the fields from the sample.
  3. Add the modules that take it from there.

Checking a request came from tufu#

Each webhook's signing secret is shown once, when you add it (or press New secret). Every request carries a Tufu-Signature header: t=<unix time>,v1=<signature>, where the signature is HMAC-SHA256 of <unix time>.<the raw body>, keyed with the secret. Check it on the raw body, before parsing it, and refuse a time more than five minutes from your clock:

python
import hashlib, hmac, time


def from_tufu(secret: str, header: str, body: bytes) -> bool:
    parts = dict(item.split("=", 1) for item in header.split(","))
    timestamp = int(parts["t"])
    if abs(time.time() - timestamp) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
javascript
const crypto = require("crypto");

function fromTufu(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(",").map((item) => item.split("=")));
  const timestamp = Number(parts.t);
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ""));
}

The id is the same on every retry of one event, so you can skip one you've already handled.

When an address doesn't answer#

tufu expects a 2xx answer within ten seconds. Otherwise it tries again: 5 tries in all, over about two and a half hours. Each try shows on the webhook's card, with the answer or the error. After 15 failed tries in a row, tufu pauses the webhook and emails your team's admins; press Resume once the address works again. Only https addresses on the public internet are accepted.