Developers / Webhooks

Webhooks carry leads into the app and finished calls out of it.

Leads in: the lead webhook

Each agent has a private lead webhook address (shown on Phone numbers and SIP to admins). Point your website form, CRM or Zapier at it. Each lead is treated as having asked to be contacted and is rung within a minute, during calling hours.

POST https://app.limekiwi.ai/api/leads/inbound/<agent id>?secret=<secret>
Content-Type: application/json   (form posts work too)

{
  "name": "Jordan Lee",          // optional
  "phone": "0412 345 678",       // required
  "email": "jordan@example.com", // optional
  "company": "Lee Builders",     // optional
  "notes": "Quote request",      // optional, read by the agent
  "source": "website"            // optional, defaults to "web form"
}

The reply is 202 with the lead's id and calling (whether the call started). A missing or invalid phone gets a 400; a wrong secret a 404.

Calls out: call webhooks

Connect Webhook, Zapier, Make or n8n on an agent's Connect tab and each finished call is posted to it as JSON:

{
  "event": "call.ended",
  "call": { "id", "url", "direction", "outcome", "started_at", "seconds",
            "headline", "summary", "next_step", "interest", "texts_sent" },
  "agent": { "id", "name", "kind", "company" },
  "contact": { "lead_id", "name", "phone", "email", "company" },
  "data": { /* the agent's Data column answers */ },
  "meeting": { "start", "minutes", "name", "email", "phone", "notes" } | null,
  "details": { /* the full call summary */ }
}

Checking the signature

Add a signing secret when connecting a webhook, and each request carries X-Signature: sha256=<hex>: an HMAC-SHA256 of the raw body with your secret. Compute it yourself and compare before trusting the request.

import { createHmac, timingSafeEqual } from "node:crypto";

const expected = "sha256=" + createHmac("sha256", SECRET).update(rawBody).digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-signature"] ?? ""));