hello ai
Sign inStart free trial
Developer docs

Webhooks

Hello AI POSTs signed JSON events to your endpoint as calls happen, so your CRM, ticketing system, or automation tool stays in sync without polling. Configure the endpoint in Dashboard → Settings → Webhooks.

Delivery

How events arrive

Verification

Verify every delivery

The signature is an HMAC-SHA256 of `${timestamp}.${rawBody}` with your signing secret (shown once when you save the webhook). Node example:

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

export function verifyHelloAiWebhook(req: { headers: Record<string, string>; rawBody: string }, secret: string): boolean {
  const ts = req.headers["x-helloai-timestamp"];
  const sig = req.headers["x-helloai-signature"]; // "sha256=<hex>"
  if (!ts || !sig) return false;
  // Reject anything older than 5 minutes (replay protection).
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = "sha256=" + createHmac("sha256", secret).update(ts + "." + req.rawBody).digest("hex");
  return expected.length === sig.length && timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

Events

Event reference

call.started

A call to your Hello AI number is beginning.

{
  "id": "evt_9c2f…",
  "event": "call.started",
  "createdAt": "2026-07-12T02:14:07Z",
  "tenantId": "561c…",
  "data": {
    "callId": "c1a2…",
    "callerNumber": "+12015550142",
    "toNumber": "+16575550110",
    "startedAt": "2026-07-12T02:14:07Z"
  }
}
call.ended

The call finished. Includes the flat transcript, structured timestamped turns, recording URL, AI summary, and a disposition (resolved | needs_followup | unresolved) — so you can file the whole record, and decide which calls need a human, without polling.

{
  "event": "call.ended",
  "data": {
    "callId": "c1a2…",
    "callerNumber": "+12015550142",
    "startedAt": "…", "endedAt": "…",
    "durationSeconds": 184,
    "endedReason": "customer-ended-call",
    "afterHours": true,
    "disposition": "needs_followup",
    "summary": "Caller requested an appointment for…",
    "transcript": "AI: Thank you for calling…\nCaller: Hi, I'd like to…",
    "recordingUrl": "https://…/recording.mp3",
    "turns": [
      { "role": "assistant", "text": "Thank you for calling…", "at": 0.4 },
      { "role": "user", "text": "Hi, I'd like to…", "at": 5.2 }
    ]
  }
}
request.created

A structured request was captured. kind is "appointment", "refill" (medical), or "message" (non-practice tenants).

{
  "event": "request.created",
  "data": {
    "requestId": "7f3b…",
    "kind": "refill",
    "urgency": "routine",
    "callerName": "Pat Doe",
    "callerPhone": "+12015550142",
    "medication": "lisinopril 10mg",
    "pharmacy": "CVS Cedar Grove",
    "notes": "Medication: lisinopril 10mg · Pharmacy: CVS…",
    "afterHours": false,
    "callId": "c1a2…"
  }
}
escalation.started

An urgent after-hours call entered the on-call cascade (medical practices).

{
  "event": "escalation.started",
  "data": {
    "escalationId": "e88a…",
    "callerName": "Pat Doe",
    "callerNumber": "+12015550142",
    "reason": "post-op bleeding",
    "callId": "c1a2…"
  }
}

A pingevent with the same envelope is sent by the "Send test event" button in Settings.