Server URL & Webhooks
Receive your agent's tool calls and phone/SMS lifecycle events on your own server.
Overview — two kinds of webhook
There are two ways the platform calls out to a URL you control. Both POST JSON, both can carry auth headers you set, and both are guarded so they can only ever reach a public address (never an internal/metadata host).
| Kind | Fires when | Configured on |
|---|---|---|
| Tool webhook | The model decides to call one of your webhook tools during a call/chat | A tool in the Tool Library (per-tool Server URL) |
| Phone Server URL | A call or SMS thread reaches a lifecycle moment (started, ended, recording ready) | A phone number's Server URL (per-number) |
Tool webhooks — payload
When the model calls one of your webhook tools, we POST to that tool's Server URL. The body always contains the model-chosen `args` plus the call context so you can attribute the request:
POST https://your-api.com/lookup ← the tool's Server URL
Content-Type: application/json
X-Your-Auth: <any header you configured>
{
"tool": "lookup_order", // the tool name
"args": { "order_number": "SBX-2291" }, // arguments the model chose
"arguments": { "order_number": "SBX-2291" }, // legacy alias of args
"callId": "<correlation id of this turn/call>",
"agentId": "<the agent's id>",
"businessId": "<your workspace id>",
"timestamp": "2026-06-22T14:00:00.000Z"
}
— your server replies —
200 OK
{ "status": "ready", "items": ["Grande Latte"], "pickup": "Front counter" }Reply with HTTP 200 and a small JSON object — the agent reads its fields and speaks/writes them back to the customer. A non-200 response (or a timeout) is surfaced to the model as a tool error so it can recover gracefully.
Phone Server URL — events & payloads
Set a Server URL on a phone number and we POST a JSON notification at each lifecycle moment. Every body starts with `type`, `timestamp`, `callId`, `agentId`, and `phoneNumber`, then adds event-specific fields:
| Event | Fires when | Channel |
|---|---|---|
| call.started | An inbound voice call connects to the agent | voice |
| recording.completed | A call's recording is captured and stored | voice |
| message.started | A new inbound SMS thread begins | sms |
| message.completed | An inbound SMS turn has been answered | sms |
POST https://your-server.com/events ← the number's Server URL
Content-Type: application/json
X-Your-Auth: <any header you configured>
// call.started (voice)
{
"type": "call.started",
"timestamp": "2026-06-22T14:00:00.000Z",
"callId": "CA…", // Twilio Call SID for voice
"agentId": "<agent id>",
"phoneNumber": "+18555294709",
"phoneNumberId": "<number id>",
"callSid": "CA…",
"from": "+1…", "to": "+1…",
"channel": "voice"
}
// recording.completed (voice)
{
"type": "recording.completed", "timestamp": "…",
"callId": "<our call id>", "agentId": "…", "phoneNumber": "+1…",
"callSid": "CA…", "recordingSid": "RE…", "recordingDuration": 42,
"channel": "voice"
}
// message.started / message.completed (sms)
{
"type": "message.completed", "timestamp": "…",
"callId": "<our thread id>", "agentId": "…", "phoneNumber": "+1…",
"from": "+1…", "channel": "sms"
}Auth headers — verifying requests are from us
Both webhook kinds let you attach static headers that we send on every request, so your endpoint can confirm the call really came from your CustomWeb workspace. Add one or more key/value pairs (for example a shared secret) and check it server-side:
// what we send (the header you configured):
X-Webhook-Secret: a-long-random-string-you-generated
// what your endpoint does:
if (req.headers["x-webhook-secret"] !== process.env.MY_SECRET) {
return res.status(401).end(); // reject anything without your secret
}- Use a long, random value — treat it like a password.
- Prefer a custom header (e.g. X-Webhook-Secret) or an Authorization: Bearer … header.
- Always serve your endpoint over HTTPS so the header isn't sent in the clear.
Static-IP allowlisting
If your endpoint sits behind a firewall that only allows known sources, enable the static-IP option on the phone number's Server URL. Our outbound requests then originate from a fixed, allowlistable IP range so you can lock your firewall down to just us — instead of (or in addition to) checking an auth header.
How to integrate — tool webhook
- Build an endpoint that accepts a POST with a JSON body and returns a small JSON object (see the payload above). A no-code automation (Zapier / Make / n8n) works too — use its Webhook trigger URL.
- Dashboard → Tools → New Tool. Give it a snake_case name and a clear description (the model reads the description to decide when to call it).
- Paste your endpoint into the Server URL field.
- Define the Parameters (JSON Schema) so the model knows what arguments to send — see the Tools page for the schema format.
- Add an auth header if your endpoint verifies a secret.
- Attach the tool to an agent (Tools tab) and click Test to fire a real request and inspect the live response, latency, and any error before going live.
How to integrate — phone Server URL
- Build an endpoint that accepts a POST and replies 200 quickly (do heavy work asynchronously).
- Dashboard → Phone Numbers → open the number → find the Server URL field.
- Paste your endpoint URL.
- Set the timeout (default 20s, allowed 1–300s), add any auth headers, and toggle static IP if your firewall needs it.
- Save. Events then POST to your URL the next time a call connects, a recording completes, or an SMS thread starts/finishes on that number.
Where to find the info you'll need
| You need | Where it is |
|---|---|
| Tool Server URL field | Dashboard → Tools → New/Edit Tool → Server URL |
| Tool auth headers | Same Tool editor (add key/value header rows) |
| Phone Server URL field | Dashboard → Phone Numbers → open a number → Server URL |
| Server URL timeout / auth headers / static IP | Same Server URL panel on the phone number |
| Agent ID (agentId in payloads) | Open the agent → Overview tab → click the ID to copy |
| Phone number (phoneNumber in payloads) | Dashboard → Phone Numbers (the number itself, E.164 format) |
| Workspace / business ID (businessId) | Sent in every tool-webhook payload; also visible in Settings |