Call Webhooks: Inbound and Outbound Calls

Call Webhooks: Inbound and Outbound Calls

Receive call-event notifications for inbound and outbound calls. Learn to configure the Calls webhook, handle call.inbound, call.outbound and call.completed, and log calls after they finish using the completed-call payload.

The simple way: set it in the portal

You do not need to call the API to register a call webhook. It can be set from the portal directly.

  1. Log in to your Zonitel Office Portal.
  2. In the left sidebar, expand Settings and click Private Integrations.
  3. Open the Webhooks tab.
  4. Under Calls webhook, paste your Webhook URL.
  5. Click Save.

Zonitel sends notifications when an inbound call reaches the system, when an extension places an outbound call, and after a call finishes. Completed-call notifications cover inbound and outbound calls. The URL has to be valid and answer with HTTP 200 to be saved — if it does not respond correctly it is not stored, so bring your endpoint up first. To remove it later, clear the field and save again.

One URL for call events: inbound, outbound and completed-call notifications use the same Calls webhook URL. There is no separate outbound webhook field or outbound switch in Office.

The rest of this article covers the API method, for setting webhooks programmatically.


Advanced: method and receiver authentication

  1. Open Advanced under the webhook URL and enable Use a custom method or authentication.
  2. Choose the HTTP method and Authentication your receiving service requires. Methods: POST, PUT, PATCH or GET. Authentication: None, Bearer token, API key, Basic (user + password) or Custom header.
  3. Complete the fields shown for that choice and save the webhook. The receiving URL must respond with HTTP 200 for the portal to accept it.

POST with no authentication is the default. GET has no request body; the event travels as query parameters. For API key, select where to send it and enter the header or parameter name required by your receiver.

The optional receiver credentials belong to the service receiving the webhook. They are separate from the Zonitel API token and X-Client-Id used when your software calls our API. Only provide credentials intended for that receiver.

The API registration example below configures the URL. Use the portal for the Advanced options shown here.

Step-by-step: calls webhook

How It Works

  1. Register a Calls webhook URL for your client account through the portal or API.
  2. Zonitel sends call-event notifications to that URL. The Calls webhook covers inbound and outbound calls; see the available events below.
  3. Your endpoint must respond with HTTP 200 OK within 5 seconds to acknowledge each notification. POST is the default method; use Advanced in the portal if your receiver requires another supported method or authentication.

Important: The webhook URL must use HTTPS and be publicly accessible.

Available call events

  • call.inbound — Sent when an inbound call reaches the system. Use it to show the caller or start a record while the call is in progress.
  • call.outbound — Sent when an extension places a call that the phone system classifies as outbound, when the call is placed and before it is answered. This includes external numbers and the cross-domain or forwarding cases described below. Use it to open or pre-fill a record while the call is in progress. It does not confirm that the call was answered; completion details arrive later in call.completed.
  • call.completed — Sent after a call has finished. This event covers inbound and outbound calls; the direction field identifies the direction. Completed-call notifications may also identify local calls.

Log calls only after they finish

To create or update your CRM call log after completion, have your receiving integration process notifications where event_type is call.completed. Acknowledge the other notifications with HTTP 200, but do not use them to create a completed-call log. This filtering is handled in your receiving integration.

Use the completed-call id to correlate or update the record, and use the timestamps, duration and call flags from that notification. For call history, recordings and transcriptions, follow Sync Your Calls with the API.


call.inbound and call.outbound report that a call started, even if it is never answered. Do not use them to create a completed-call log.

Timing: call.outbound is generated when the number is dialed. call.completed is generated when the call detail record (CDR) is imported, which may be some time after hangup. Do not expect both events to arrive immediately one after the other.

When call.outbound is sent

  • A user's desk phone, softphone or mobile app dials an external number.
  • The start event is generated once per call, including when routing retries a backup carrier. This is not an exactly-once delivery guarantee; design your receiver to handle retries safely.
  • Unanswered and cancelled calls are included because the event is generated when the call is placed.
  • Emergency calls (911) also generate the event. Never place an emergency call to test a webhook.

When call.outbound is not sent

  • Direct internal calls between extensions in the same account/domain. These are classified as local and do not generate call.outbound. If the called extension forwards to a cell phone, see the exception below.
  • Inbound calls forwarded to an external number, including call forwarding, follow me and ring groups that ring a cell phone. Their outbound legs can still generate call.completed with direction set to outbound.
  • Faxes sent from the portal.

Cross-domain extensions and cell-phone forwarding

  • Extensions in different domains: a call between extensions in separate accounts/domains can generate call.outbound when its route is classified as outbound, even if both domains belong to the same organization. In this case, destination_number can be a short extension such as 401 or 102, rather than a public phone number.
  • An extension forwards to a cell phone: calling an extension that forwards to a cell phone can also generate call.outbound for the outbound routing. Do not assume that every call initially dialed to an extension is excluded.

These distinctions describe call.outbound only. They do not establish whether call.completed is sent for the same call. Do not infer completed-event behavior from the outbound start-event filter.

Outbound start-event availability depends on your account's phone-system deployment. If you receive completed events but no outbound start events, contact Zonitel Support to confirm availability.


Register a Webhook URL

Endpoint:

 
PUT https://api.zonitel.com/api/v3/integrations/calls/webhooks Content-Type: application/json X-Client-Id: 550e8400-e29b-41d4-a716-446655440000 Authorization: Bearer <YOUR_ACCESS_TOKEN>

Request Body (JSON):

 
{ "url": "https://yourdomain.com/api/call-events" }

Notes:

  • URL must be HTTPS and reachable from the public internet.

  • Must respond with HTTP 200 OK within 5 seconds.

Success Response (200 OK):

 
{ "status": "success", "data": { "url": "https://yourdomain.com/api/call-events" }, "message": "Webhook updated successfully" }

Retrieve Registered Webhook

Endpoint:

 
GET https://api.zonitel.com/api/v3/integrations/calls/webhooks X-Client-Id: 550e8400-e29b-41d4-a716-446655440000 Authorization: Bearer <YOUR_ACCESS_TOKEN>

Example Response (200 OK):

 
{ "status": "success", "data": { "url": "https://yourdomain.com/api/call-events" }, "message": "Webhook retrieved successfully" }

Webhook payload examples

These are notifications received by your endpoint, not the body used to register the webhook. The examples below show the default POST delivery.

Inbound call starts: call.inbound

{
  "event_type": "call.inbound",
  "caller_id_name": "John Smith",
  "caller_id_number": "13055550123",
  "destination_number": "13055550999",
  "direction": "inbound"
}

Outbound call starts: call.outbound

{
  "event_type": "call.outbound",
  "caller_id_name": "Example Company",
  "caller_id_number": "13055550999",
  "destination_number": "3055550123",
  "direction": "outbound"
}

The current outbound notification does not include id or extension. Do not rely on a shared identifier to join it to call.completed. Use the completed-call id for the final call record.

Call finishes: call.completed

This example is an outbound call. An inbound completed call uses the same event type with direction set to inbound.

{
  "event_type": "call.completed",
  "id": "a3c24a95-8e47-4ce7-a7a5-f46197b185bd",
  "caller_id_name": "Example Company",
  "caller_id_number": "13055550999",
  "destination_number": "3055550123",
  "destination_name": "John Smith",
  "duration": "42",
  "is_missed_call": false,
  "has_voicemail": false,
  "start_stamp": {
    "date": "2026-10-13 18:30:00.000000",
    "timezone_type": 1,
    "timezone": "+00:00"
  },
  "answer_stamp": {
    "date": "2026-10-13 18:30:04.000000",
    "timezone_type": 1,
    "timezone": "+00:00"
  },
  "end_stamp": {
    "date": "2026-10-13 18:30:46.000000",
    "timezone_type": 1,
    "timezone": "+00:00"
  },
  "direction": "outbound"
}

Payload fields

  • event_type: Identifies the notification: call.inbound, call.outbound or call.completed.
  • id: Call identifier in call.completed; it is not included in the current call.outbound notification.
  • caller_id_name and caller_id_number: Caller identification. In call.outbound, these are the caller ID sent to the carrier (the company caller ID), not the extension number.
  • destination_number and destination_name: Destination number and name, when supplied. In call.outbound, destination_number is the number as dialed; its format may vary (for example, 10 digits, 11 digits or an international number with 011). It can also be a short extension for cross-domain calls classified as outbound. Preserve it as a string; do not assume a fixed length or a public phone number.
  • direction: Call direction; completed calls may be inbound, outbound or local.
  • duration: Reported call duration in seconds.
  • is_missed_call and has_voicemail: Indicate a missed call and voicemail.
  • start_stamp, answer_stamp and end_stamp: Call timestamps, including their timezone information when supplied.

Common Error Responses

  • 401 Unauthorized

 
{ "code": 401, "message": "Authentication required" }
  • 403 Forbidden

 
{ "code": 403, "message": "Access denied" }
  • 404 Not Found

 
{ "code": 404, "message": "No webhook URL configured." }
  • 500 Internal Server Error

 
{ "code": 500, "message": "An internal server error occurred" }

Best Practices

  • Always use HTTPS for your webhook URLs.

  • Respond with HTTP 200 OK within 5 seconds to acknowledge the call event.

  • Keep your Bearer token secure.

  • Ensure your webhook endpoint can handle multiple concurrent calls.

Remove a Registered Webhook

Send DELETE to the same address to stop receiving events. Clearing the Calls field on the Webhooks tab of Settings > Private Integrations does the same thing.

DELETE https://api.zonitel.com/api/v3/integrations/calls/webhooks

Going further

Registering the webhook is one half of a call integration. Pulling call history, downloading recordings and transcriptions, and placing calls from your own app are covered in Sync Your Calls with the API.

Need help?

Call/Text/WhatsApp: (833) 966-4835
Email: info@zonitel.com

Was this article helpful?

Still Have Questions?

Our support team is available 24/7 to help you get started

Chat with us on WhatsApp