CallBooker Lead API

Introduction

Send leads to CallBooker and your AI agent calls them within seconds, qualifies them and books meetings on your calendar. The API lets you add leads, follow their calls and meetings, and get results pushed to your systems with webhooks.

Base URL: https://callbooker.com/api/v1. Requests and responses are JSON (form-encoded requests are accepted too). Dates are ISO 8601 in UTC.

Quickstart

1. Create an API key in Dashboard → Lead API. Pick the agent that will make the calls.

2. Send a lead. The agent starts calling right away.

curl -X POST https://callbooker.com/api/v1/leads \
  -H "Authorization: Bearer cb_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sarah Johnson",
    "phone": "+14155550142",
    "email": "sarah@example.com",
    "notes": "Requested a home valuation for a 3-bedroom in Austin"
  }'

3. Get the results. Add a webhook to receive the call result and booked meetings automatically, or fetch the lead later with GET /leads/:id.

The notes and any extra form answers are given to the agent before the call, so it opens with something relevant (“I saw you asked about a valuation for your 3-bedroom…”).

Authentication

The API, webhooks and MCP are included on the Starter plan and above.

Send your API key in the Authorization header:

Authorization: Bearer cb_live_YOUR_KEY

Each key belongs to one agent: every call made with it uses that agent. Create one key per agent or per source (website, CRM) so you can revoke them separately. The X-API-Key header works too.

Keep keys on your server. Never put a key in browser JavaScript or a mobile app: anyone who finds it can place calls on your account. There is no CORS support. From a website form, send the lead from your backend or through Zapier/Make.

Phone numbers

Numbers are validated and stored in international format (E.164, e.g. +14155550142). You can send them in any common format:

  • With a country code: +1 (415) 555-0142, 0033 6 12 34 56 78.
  • Without one, together with country: "phone": "(415) 555-0142", "country": "US".
  • Without one, if the key has a default country (set it in the dashboard, next to the key).

A number that isn't valid for its country is rejected with invalid_phone and a message saying why. We never guess a country.

Leads

Don't book meetings? Set the agent's call goal to Qualify & report. It qualifies leads without booking, and the result (interested, callback…), the agent's notes and the answers come back here and in webhooks, so your system can follow up its own way.

A lead is a person your agent calls. Leads sent through the API are filed in the Website Leads list. Sending a phone number you already have updates that lead instead of creating a duplicate.

The lead object

idstring
Unique id.
name, phone, email, companystring | null
Contact details. phone is E.164.
statusstring
One of not-called, interested, callback, booked, not-interested, no-answer, voicemail, do-not-call. Set by the agent after each call.
agent_notesstring | null
The agent's summary of the last call: what they need, objections, best time to reach them.
notes, fieldsstring | null, object
What you sent with the lead (form message and answers).
answersobject
Answers the agent collected for its “What to find out” questions, e.g. {"budget": "5k", "team_size": "8"}. Set the questions in the agent editor.
callback_atstring | null
When the lead asked to be called back.
do_not_callboolean
The lead asked not to be called again. Calls to them are refused.
upcoming_meetingmeeting | null
Their next booked meeting (single lead only).
latest_call_id, call_count, last_called_at
Their call history at a glance.
lead
{
  "id": "Xk2vL9qPz3",
  "object": "lead",
  "name": "Sarah Johnson",
  "phone": "+14155550142",
  "email": "sarah@example.com",
  "company": "Acme Realty",
  "status": "interested",
  "agent_notes": "Selling in 2 months, wants to compare two agencies.",
  "notes": "Requested a home valuation",
  "fields": { "budget": "5k", "bedrooms": "3" },
  "source": "api",
  "do_not_call": false,
  "callback_at": null,
  "call_count": 1,
  "last_called_at": "2026-10-03T14:02:11.000Z",
  "latest_call_id": "c9Fh27WkQa",
  "upcoming_meeting": null,
  "created_at": "2026-10-03T14:01:58.000Z"
}

Create a lead

POST/v1/leads

Adds the lead and calls it immediately with the key's agent. Returns the lead with its call.

phonestring, required
The number to call. See Phone numbers.
countrystring
Two-letter country code (US, GB, FR…) for numbers without a country code.
namestring
Full name. first_name + last_name work too. Without a name, the agent greets them with “Hi there”.
emailstring
Where calendar invites go when a meeting is booked.
companystring
Their company.
notesstring
What they asked for or wrote. Also accepted as message or comments.
fieldsobject
Any other form answers, e.g. {"budget": "5k"}. Up to 30. Unknown top-level fields are added here automatically, so you can post a whole form as-is.
callboolean
Default true. Send false to add the lead without calling.

No duplicate calls. If the same number is sent again within 30 minutes (double form submits, retries), you get the existing lead and call back with "duplicate": true and status 200 instead of a second call. New leads return 201.

curl -X POST https://callbooker.com/api/v1/leads \
  -H "Authorization: Bearer cb_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sarah Johnson",
    "phone": "(415) 555-0142",
    "country": "US",
    "email": "sarah@example.com",
    "company": "Acme Realty",
    "notes": "Requested a home valuation",
    "fields": { "budget": "5k", "bedrooms": "3" }
  }'

Retrieve a lead

GET/v1/leads/:id

Returns the lead, including the agent's notes and their upcoming meeting.

curl https://callbooker.com/api/v1/leads/Xk2vL9qPz3 \
  -H "Authorization: Bearer cb_live_YOUR_KEY"

List leads

GET/v1/leads

Newest first. All leads in your account, from every source.

statusstring
Only leads with this status, e.g. interested.
sourcestring
api, csv, manual or single-call.
created_afterdate
Only leads created after this date.
limit, starting_after
curl "https://callbooker.com/api/v1/leads?status=interested&limit=50" \
  -H "Authorization: Bearer cb_live_YOUR_KEY"

Call a lead again

POST/v1/leads/:id/call

Starts a new call to the lead with the key's agent. Returns the call (201). Refused with call_in_progress while they're on a call, and with do_not_call if they asked not to be called.

Calls

The call object

statusstring
queued, in_progress, completed or failed.
resultstring | null
How it ended, once finished: booked, interested, callback, not-interested, do-not-call, voicemail, no-answer, busy, canceled, talked (a real conversation, no outcome recorded), ended (picked up, no conversation) or failed.
agent_notesstring | null
The agent's summary of the call.
answersobject
The answers collected on this call (see the lead object).
duration_secondsnumber | null
Length of the call.
transcriptstring | null
The full conversation, once finished.
recording_urlstring | null
The call's audio, for connected calls. Fetch it with your API key (Starter plan and up); see Get a recording.
meeting_idstring | null
The meeting booked on this call.
errorstring | null
Why a failed call didn't connect.
sourcestring
api, single (dashboard) or campaign.
call
{
  "id": "c9Fh27WkQa",
  "object": "call",
  "lead_id": "Xk2vL9qPz3",
  "status": "completed",
  "result": "booked",
  "agent": { "id": "a81kd02", "name": "Sales Agent" },
  "to": "+14155550142",
  "duration_seconds": 184,
  "agent_notes": "Booked a demo for Thursday 3 PM. Team of 5, using spreadsheets today.",
  "error": null,
  "transcript": "Agent: Hi Sarah, this is Alex from Acme…",
  "recording_url": "https://callbooker.com/api/v1/calls/c9Fh27WkQa/recording",
  "meeting_id": "mT4ss81Lp",
  "source": "api",
  "campaign_id": null,
  "created_at": "2026-10-03T14:02:11.000Z",
  "ended_at": "2026-10-03T14:05:20.000Z"
}

Retrieve a call

GET/v1/calls/:id

List calls

GET/v1/calls

Newest first. Filter with lead_id for one lead's calls.

curl "https://callbooker.com/api/v1/calls?lead_id=Xk2vL9qPz3" \
  -H "Authorization: Bearer cb_live_YOUR_KEY"

Get a recording

GET/v1/calls/:id/recording

Returns the call's audio as an MP3 file. Available on the Starter plan and above, for calls up to 30 days old (90 on Growth, 365 on Scale). Errors: plan_required (403), no_recording (404, for example a call that never connected) and recording_expired (410).

curl "https://callbooker.com/api/v1/calls/c9Fh27WkQa/recording" \
  -H "Authorization: Bearer cb_live_YOUR_KEY" \
  -o call.mp3

Meetings

Meetings your agents booked on your connected calendar (Google Calendar or Calendly).

The meeting object

statusstring
booked, moved (replaced by moved_to) or cancelled.
start_time, end_timestring
In UTC. time_zone is the calendar’s timezone.
attendedboolean | null
Set when you mark the meeting attended or no-show in the dashboard.
video_linkstring | null
Google Meet link, when your settings add one.
invite_sent_tostring | null
Where the calendar invite went.
meeting
{
  "id": "mT4ss81Lp",
  "object": "meeting",
  "lead_id": "Xk2vL9qPz3",
  "call_id": "c9Fh27WkQa",
  "lead_name": "Sarah Johnson",
  "status": "booked",
  "start_time": "2026-10-08T19:00:00.000Z",
  "end_time": "2026-10-08T19:30:00.000Z",
  "duration_minutes": 30,
  "time_zone": "America/New_York",
  "attended": null,
  "video_link": "https://meet.google.com/abc-defg-hij",
  "calendar": "google",
  "invite_sent_to": "sarah@example.com",
  "moved_to": null,
  "cancel_reason": null,
  "agent": { "id": "a81kd02", "name": "Sales Agent" },
  "created_at": "2026-10-03T14:04:52.000Z"
}

List meetings

GET/v1/meetings

By start time, earliest first. GET /v1/meetings/:id returns one.

from, todate
Meetings starting in this range. Use from=now’s date for upcoming meetings.
statusstring
booked, moved or cancelled.
limit, starting_after
See Pagination.
curl "https://callbooker.com/api/v1/meetings?from=2026-10-01&status=booked" \
  -H "Authorization: Bearer cb_live_YOUR_KEY"

Account

GET/v1/me

Checks that a key works and shows what it's connected to. Use it as the “test connection” step in no-code tools.

{
  "object": "account",
  "email": "you@company.com",
  "plan": "growth",
  "agent": { "id": "a81kd02", "name": "Sales Agent", "phone_number": "+14845550199", "ready_to_call": true },
  "minutes": { "used": 312, "limit": 1000, "remaining": 688 },
  "key": { "label": "Website form", "default_country": "US" }
}

Webhooks

Webhooks send events to your server the moment they happen: a call ends, a meeting is booked. Add an endpoint in Dashboard → Lead API → Webhooks, choose the events, and use Send test event to check it. Every delivery, with your server's answer, is listed there.

Events

call.completedcall
A call ended. Includes the result, duration, agent notes and transcript.
lead.status_changedlead
A lead's status changed (e.g. to interested or callback). Includes previous_status.
meeting.bookedmeeting
The agent booked a meeting.
meeting.movedmeeting
A meeting moved to a new time. The new meeting, with previous_meeting_id.
meeting.cancelledmeeting
A meeting was cancelled by the lead or by you.
meeting.attendedmeeting
You marked the lead as having shown up (one click in the email, or in the dashboard).
meeting.no_showmeeting
You marked the lead as a no-show.

Events cover all calls in your account (API, dashboard and campaigns). Use source on the call to tell them apart.

Payload

A POST with a JSON body. data.object is the same object the API returns.

{
  "id": "evt_069f25df79c54a62905c5767",
  "object": "event",
  "type": "call.completed",
  "created_at": "2026-10-03T14:05:21.000Z",
  "test": false,
  "data": {
    "object": {
      "id": "c9Fh27WkQa",
      "object": "call",
      "status": "completed",
      "result": "booked",
      "agent_notes": "Booked a demo for Thursday 3 PM…",
      "…": "…"
    }
  }
}

Headers: CallBooker-Event (the type), CallBooker-Delivery (unique per delivery) and CallBooker-Signature.

Verifying signatures

Check that a request really comes from CallBooker. The CallBooker-Signature header looks like t=1759500321,v1=5257a8…. Compute an HMAC-SHA256 of {t}.{raw body} with your endpoint's signing secret (whsec_…) and compare it to v1. Use the raw body exactly as received, before parsing JSON.

import crypto from "crypto";
import express from "express";

const app = express();
const SECRET = process.env.CALLBOOKER_WEBHOOK_SECRET; // whsec_…

app.post("/callbooker/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("CallBooker-Signature") || "";
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${req.body}`).digest("hex");

  const valid = v1 && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; // reject replays
  if (!valid || !fresh) return res.status(400).send("Bad signature");

  const event = JSON.parse(req.body);
  if (event.type === "meeting.booked") {
    // e.g. update the deal in your CRM
  }
  res.sendStatus(200);
});

Retries

Answer with any 2xx status within 8 seconds. Do slow work after answering. Otherwise we retry after 1 minute, 5 minutes, 30 minutes, 2 hours and 8 hours, then mark the delivery failed. You can resend any delivery from the dashboard. Retries mean an event can arrive more than once: use the event id to ignore duplicates. Endpoints must be public https:// addresses.

Pagination

List endpoints return { "object": "list", "data": [...], "has_more": true, "next_cursor": "id" }. Use limit (1–100, default 20) and pass next_cursor as starting_after to get the next page.

let cursor = null;
do {
  const url = new URL("https://callbooker.com/api/v1/leads");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("starting_after", cursor);
  const page = await (await fetch(url, { headers: { Authorization: `Bearer ${key}` } })).json();
  for (const lead of page.data) console.log(lead.name, lead.status);
  cursor = page.next_cursor;
} while (cursor);

Errors

Errors use HTTP status codes and a JSON body that says exactly what to fix:

{
  "error": {
    "code": "invalid_phone",
    "message": "phone \"4155550142\" has no country code. Send it as +14155550142, or pass \"country\" (e.g. \"US\").",
    "param": "phone"
  }
}
400 invalid_request
A field is missing or wrong. param names it.
400 invalid_phone
The phone number isn't valid. The message says why.
401 unauthorized
Missing, wrong or revoked API key.
403 plan_required
Your plan doesn’t include the API. It’s available on Starter and above.
403 agreement_required
Accept the calling agreement in your dashboard before calls can be made.
402 quota_exceeded
Your account is out of call minutes. Upgrade or wait for the next billing period.
404 not_found
No such lead, call, meeting or endpoint.
409 do_not_call
The lead asked not to be called again.
409 call_in_progress
The lead is on a call right now.
409 agent_not_ready
The key's agent can't call yet (no phone number, deleted…). The message says what to fix.
429 rate_limited
Too many requests. Wait for Retry-After seconds.
500 internal_error
Our side. Safe to retry; duplicate protection prevents double calls.

Every request and its error is listed in the dashboard's Request log for 30 days.

Rate limits

60 requests per minute per key (300 on the Scale plan). Every response includes X-RateLimit-Limit and X-RateLimit-Remaining; a 429 includes Retry-After (seconds). Need more? Contact us.

Zapier, Make & forms

No code needed: any tool that can send a web request can send leads, and receive results with webhooks.

Zapier

  1. Trigger: your form or lead source (Typeform, Webflow, HubSpot, Facebook Lead Ads, Google Sheets…).
  2. Action: Webhooks by Zapier → POST. URL https://callbooker.com/api/v1/leads, payload type json.
  3. Data: map name, phone, email, notes (and country if numbers have no country code).
  4. Headers: Authorization = Bearer cb_live_YOUR_KEY.

To get results back, create a Zap with Webhooks by Zapier → Catch Hook and add its URL as a webhook in the dashboard.

Make

Add an HTTP → Make a request module: method POST, URL https://callbooker.com/api/v1/leads, header Authorization: Bearer cb_live_YOUR_KEY, body type JSON with the lead's fields. For results, use a Webhooks → Custom webhook trigger and add its URL in the dashboard.

Website forms

Post the form to your own backend and forward it to /v1/leads, or use Zapier/Make. Common field names work as they are (first_name, last_name, phone_number, message…), form-encoded bodies are accepted, and any other answers end up in fields for the agent to use.

Older integrations

The original endpoint POST https://us-central1-callbooker-sdr.cloudfunctions.net/captureLead keeps working with its original response. New integrations should use /v1/leads.