Webhooks and API basics
Send leads into NovaReps with the API, and get call data out of it with webhooks.
Updated Oct 29, 2026 · 11 min read
On this page
- Two directions
- API: push a lead in
- GET /api/v1/me
- POST /api/v1/campaigns/leads
- Chat API: talk to your agent in writing
- Getting the reply
- Handing over to a person
- Replies you might get
- Webhooks: get data out
- Events
- Calls
- Appointments
- Outbound campaigns
- Messages and compliance
- AI assistants (MCP): ask Claude or ChatGPT about your calls
- Related Articles
Find both under Integrations in the sidebar: API (/dashboard/api-webhooks) and Webhooks (/dashboard/webhook-endpoints). Both need a plan that includes them.
Two directions #
| API (into NovaReps) | Webhooks (out of NovaReps) | |
|---|---|---|
| What it does | You send us a lead to call | We send your system a message when something happens |
| Who starts it | Your system | NovaReps |
| Needs | An API key | A public HTTPS URL |
API: push a lead in #
- Open Integrations, then API.
- Enter a key name, pick an outbound agent and create the key. You are asked for your password. The key is shown once, so copy it straight away.
- Send it as a header on every request:
Authorization: Bearer <key>.
Two endpoints push leads in. If your plan includes text conversations there is also a Chat API.
GET /api/v1/me #
Tests that your key works.
POST /api/v1/campaigns/leads #
Adds a lead to the campaign linked to your key, to be called by its outbound agent.
| Field | Required | Notes |
|---|---|---|
phone_number |
Yes | For example +15551234567. Common formats such as (555) 123-4567 are converted for you. |
first_name |
No | Up to 100 characters. |
last_name |
No | Up to 100 characters. |
curl -X POST https://YOUR-DOMAIN/api/v1/campaigns/leads \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone_number": "+15551234567", "first_name": "Jane", "last_name": "Doe"}'
Replies:
201with"queued": true: the lead was added.200with"duplicate": true: that number is already in the campaign, so it was skipped.403: the key is not allowed to send leads, or your plan does not include the API.422: the phone number could not be recognised.
There is no endpoint to list calls or agents. To get call data out, use webhooks.
Chat API: talk to your agent in writing #
Use the Chat API to let your own website, app or backend hold written conversations with any of your inbound agents. It needs a plan that includes text conversations. Each agent reply uses one of your plan's text messages.
- Open Integrations, then API, and find the Chat API section.
- Choose Create chat API key, enter a name and your password. The key is shown once, so copy it straight away.
- Send it on every request as
Authorization: Bearer <key>. A chat key can reach every inbound agent on your account. It cannot push leads, and a lead key cannot chat.
| Request | What it does |
|---|---|
POST /api/v1/agents/{agent}/conversations |
Starts a conversation. Optional body: message, external_user_id, phone, metadata. |
POST /api/v1/conversations/{id}/messages |
Sends a customer message. Body: text (up to 4000 characters), optional idempotency_key. |
GET /api/v1/conversations/{id} |
Returns the status and every message so far. |
POST /api/v1/conversations/{id}/end |
Ends the conversation now. |
Conversation IDs look like chat_01J9ZK8W2V6X4Q7T3N5R1B0M9D. An agent or conversation that is not yours returns 404.
Getting the reply #
By default the reply is written in the background. The message request returns 202 with a message_id. Then either read the conversation with GET, or subscribe to the Chat Reply Sent webhook (conversation.message).
To get the reply in the same request, add ?wait=8 (1 to 10 seconds). You get 200 with the reply when it is ready, or 202 with the current status if it is not.
curl -X POST "https://YOUR-DOMAIN/api/v1/agents/12/conversations?wait=8" \
-H "Authorization: Bearer YOUR_CHAT_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "What time do you open?", "external_user_id": "user_8841"}'
Sending the same idempotency_key again returns the same message_id and does not trigger a second reply, so retries are safe.
Handing over to a person #
If the customer asks for a person, the agent hands the conversation over. It stops replying, the conversation status becomes paused with handoff_reason set to human_requested, you get an email, and a Text Conversation Handed Off webhook (conversation.handoff) is sent with the transcript so far. New customer messages are still saved. Open the email link to reply from the conversation page in Call History (the customer sees your reply in the chat or as a text), or reply from your own tools. Replies from your team appear in GET conversation with sender set to team, and also fire conversation.message. The same happens, with reason quota_exhausted, when your plan's text messages run out.
Replies you might get #
403: your plan does not include text conversations, or the key is not a chat key.404: the agent or conversation is not on your account.409: the agent is switched off, or the conversation has ended.422: a field is missing or too long.429: too many requests, or too many open conversations. Wait a moment and try again.
Webhooks: get data out #
We send a JSON POST to your HTTPS URL when something happens, such as a call ending or an appointment being booked.
NovaReps sends the full call record, including the transcript and summary, with the Transcript Ready event (transcript.ready). Call Completed (call.completed) arrives first with summary set to null.
Every message looks like this:
{
"id": "evt_...",
"type": "call.completed",
"api_version": "2026-04-07",
"created_at": "2026-01-15T14:05:00+00:00",
"data": { "object": { } }
}
Headers sent with every request:
X-Webhook-Signature: proves the message came from us.X-Webhook-Event: the event type.X-Webhook-ID: the event ID, the same on every retry.User-Agent:AIReceptionist-Webhooks/1.0
To set one up, follow the setup guide.
Events #
Calls #
| Event | Name in the app | When it is sent |
|---|---|---|
call.started |
Call Started | Sent when a call begins (for outbound, when dialling starts). Carries the call ID, direction, channel, numbers and agent. |
call.completed |
Call Completed | Sent when a call ends. Carries the full call record. The summary is not written yet at this point, so use Transcript Ready if you need it. |
transcript.ready |
Transcript Ready | Sent a few seconds after a call ends, once the summary is written. This is the complete record: caller details, collected data, full transcript and summary. |
recording.ready |
Recording Ready | Sent when the call recording is available. Carries the full call record with the recording link (only if "Include recording link" is on). |
call.missed |
Call Missed | Sent when an inbound phone call fails, is busy, is not answered or is cancelled before a conversation happens. |
call.transferred |
Call Transferred | Sent when the AI hands the caller to a human. Carries the full call record so far and the number it was transferred to. |
call.message_taken |
Message Taken | Sent when the AI takes a message for your team. Carries the full call record including the message. |
call.callback_requested |
Callback Requested (Inbound) | Sent when a caller asks to be called back. Carries the call details and the requested time. |
caller.created |
New Caller | Sent the first time a phone number calls one of your agents. Carries the number, the caller name if known and the agent. |
Appointments #
| Event | Name in the app | When it is sent |
|---|---|---|
call.appointment_booked |
Appointment Booked | Sent the moment a booking is confirmed, without waiting for the call to end. Carries the appointment time and the booking details. |
appointment.rescheduled |
Appointment Rescheduled | Sent when a caller moves an existing appointment. Carries the call details and the new time. |
appointment.cancelled |
Appointment Cancelled | Sent when a caller cancels an appointment. Carries the call details. |
appointment.confirmed |
Appointment Confirmed (Text Reply) | Sent when a caller confirms an appointment by replying to a reminder text. Carries the call details and the appointment time. |
Outbound campaigns #
| Event | Name in the app | When it is sent |
|---|---|---|
lead.created |
Lead Created (Inbound API) | Sent when a lead arrives through the API and is queued for calling. Carries the contact and campaign. |
lead.captured |
Lead Captured (Website Widget) | Sent when a visitor submits the lead form on your website widget, before any call starts. Carries the name, email, phone, page and campaign tags. The details are typed by the visitor and not verified. |
lead.qualified |
Lead Qualified | Sent as soon as the AI records answers to your qualifying questions, before the call ends. Carries the contact and the answers. |
outbound_contact.completed |
Outbound Contact Completed | Sent when an outbound call to a contact finishes. Carries the outcome, qualifying answers, appointment details and transcript. |
outbound_contact.not_reached |
Outbound Contact Not Reached | Sent each time an attempt to reach a contact fails (no answer, failed or skipped). Carries the attempt count and whether another attempt is planned. |
voicemail.detected |
Voicemail Detected (Outbound) | Sent when an outbound call reaches voicemail. Carries the call details. |
campaign.started |
Campaign Started | Sent when a campaign starts or resumes dialling. |
campaign.paused |
Campaign Paused | Sent when a campaign is paused. |
campaign.failed |
Campaign Failed | Sent when a campaign stops because of an error. |
campaign.completed |
Campaign Completed | Sent when every contact in a campaign has been processed. Carries the totals. |
Messages and compliance #
| Event | Name in the app | When it is sent |
|---|---|---|
sms.received |
Text Message Received | Sent when someone replies by text to one of your numbers. Carries both numbers and the message text. |
dnc.added |
Number Added to DNC List | Sent when a number is added to your Do Not Call list. Carries the number, the source and the reason. |
conversation.message |
Chat Reply Sent | Sent each time the agent replies in a chat or text conversation. Carries the conversation ID and the reply, so an async Chat API client can receive replies without polling. |
conversation.handoff |
Text Conversation Handed Off | Sent when a chat or text conversation needs a person: the agent stopped replying (for example the plan's text messages ran out). Carries the conversation record and a reason code. |
The recording link is null unless Include recording link is on for the endpoint. The link works without logging in and expires after 24 hours.
AI assistants (MCP): ask Claude or ChatGPT about your calls #
Connect an AI assistant to your account and ask it questions such as "How many calls did we miss this week?" or "Summarise yesterday's leads". It can read your agents, calls and text conversations, call statistics, recent callers and outbound campaigns. It cannot change anything.
- In NovaReps, open Integrations, then API, and find the AI assistants (MCP) card.
- Choose Copy URL.
- In Claude, open Settings, then Connectors, add a custom connector and paste the URL. In ChatGPT, add a custom app or connector and paste the same URL. In Claude Code, run
claude mcp add --transport http assistant <the URL>. - Sign in to NovaReps when asked. A page shows what the assistant will be able to read. Choose Allow.
- Ask your question in the assistant, for example "How many calls did we miss this week?".
The assistant can look up your agents, your calls and conversations (with summaries), call statistics, recent callers and your outbound campaigns. It sees the same data you see, and no more. A team member only reaches what their role allows. The recording of a call is never shared, and a transcript is only read when you ask about the exact words.
To disconnect an app, choose Revoke next to it in the Connected apps list. It loses access straight away and you can connect it again later. If a team member is suspended or removed, their connected apps stop working at once. This section needs a plan that includes API access, and the assistant must be able to reach your site over the internet.
Why can't my assistant connect? Check that you copied the full URL, that your plan includes API access, and that you are signed in as someone allowed to see the data. If it still fails, contact support.