๐Ÿ“ง sales@zvmsindustries.com

๐Ÿ“ž 833.208.0002

API Docs / Webhooks

Webhooks

Webhooks notify your server in real time when something happens to a call on your phone system, such as when it starts ringing, is answered, or ends. Use them instead of polling to react to calls as they happen.

POST{YOUR_WEBHOOK_URL}

Setup

๐Ÿ”

Webhooks are enabled by request. There's no self-service webhook setup. Contact our team and we'll configure webhooks for your domain once your request is approved.

  1. Send us the HTTPS URL where you want to receive notifications.
  2. Tell us which event types you want. You can enable any combination.
  3. Once approved, we enable delivery to your URL and send you your signing secret and verification details.
  4. Optionally, ask us for a call.test delivery to confirm your endpoint receives and verifies requests.

Event Types

EventType valueSent when
Call Ringingcall.ringingA call starts ringing at a destination, such as an extension.
Call Answeredcall.answeredA call is answered.
Call Endedcall.endedA call ends.
Testcall.testWe send a test delivery to check your endpoint.
๐Ÿ”€

One call can send several events. When a call rings more than one extension at once, such as through a ring group, each leg sends its own events with its own interaction_id. Legs that don't answer end with call.ended too. For example, a leg that lost to another extension that answered first reports "hangup_cause": "LOSE_RACE".

Request Headers

Each webhook is an HTTPS request with a JSON body and these headers:

HeaderDescription
content-typeAlways application/json.
user-agentZVMS-Call-Webhooks/1.0
x-zvms-event-typeThe event type, for example call.ended. Matches the body's type field.
x-zvms-event-idUnique ID of this event. Use it to detect duplicate deliveries.
timestampWhen the request was sent, as a Unix epoch timestamp in seconds.
signatureHMAC-SHA256 signature of the request, generated with your signing secret. See Verifying Requests.
Example headers
content-type: application/json
user-agent: ZVMS-Call-Webhooks/1.0
x-zvms-event-type: call.ended
x-zvms-event-id: 28f5cb99-dfa3-4490-8454-8290ce9f24e3
timestamp: 1793346975
signature: d4d139ce1cd3a94174008a5614167ee7b1f415b0a9db036c85d25f21247f6751

Payload

Every event uses the same body structure. The state object fills in as the call progresses.

FieldTypeDescription
idstring (UUID)Unique ID of the event. Same value as the x-zvms-event-id header.
typestringThe event type, for example call.ringing.
occurred_atstringWhen the event happened, as an ISO 8601 timestamp in UTC.
data.interaction_idstring (UUID)ID of the call leg the event is about. Events for the same leg share this ID.
data.channel_uuidstring (UUID)The call leg's channel ID.
data.domain_uuidstring (UUID)Your domain's UUID.
data.directionstringCall direction, for example inbound.
data.caller.namestringCaller ID name. URL-encoded.
data.caller.numberstringCaller ID number. URL-encoded, so + arrives as %2B.
data.destination_numberstringThe number or extension the call was sent to.
data.targetobjectWhere the call leg was routed. See Target fields.
data.state.answered_atstring or nullWhen the leg was answered (ISO 8601, UTC), or null.
data.state.ended_atstring or nullWhen the leg ended (ISO 8601, UTC), or null.
data.state.outcomestring or nullOutcome of the leg, set on call.ended, for example failed.
data.state.hangup_causestring or nullTelephony hangup cause, set on call.ended, for example LOSE_RACE.
๐Ÿ”ฃ

Decode caller ID values. caller.name and caller.number are URL-encoded. For example, %2B15555550123 decodes to +15555550123. Decode them before displaying or matching them.

Target fields

FieldDescription
typeThe kind of destination, for example extension.
extension_uuid, extensionThe extension's ID and number.
call_center_agent_uuid, agent_id, agent_nameThe call center agent, when the call was routed to one. Otherwise null.
call_center_queue_uuid, queue_extension, queue_nameThe call center queue, when the call came through one. Otherwise null.

Example: call.ended

JSON
{
  "id": "28f5cb99-dfa3-4490-8454-8290ce9f24e3",
  "type": "call.ended",
  "occurred_at": "2026-10-28T17:59:56.802Z",
  "data": {
    "interaction_id": "4a4d5e6e-6f75-4c70-8310-84b4e9bd678c",
    "channel_uuid": "4a4d5e6e-6f75-4c70-8310-84b4e9bd678c",
    "domain_uuid": "YOUR_DOMAIN_UUID",
    "direction": "inbound",
    "caller": {
      "name": "JANE%20DOE",
      "number": "%2B15555550123"
    },
    "destination_number": "110",
    "target": {
      "type": "extension",
      "extension_uuid": "4cf62e35-75e6-4711-a644-12e07650f602",
      "extension": "110",
      "call_center_agent_uuid": null,
      "agent_id": null,
      "agent_name": null,
      "call_center_queue_uuid": null,
      "queue_extension": null,
      "queue_name": null
    },
    "state": {
      "answered_at": null,
      "ended_at": "2026-10-28T17:59:56.802Z",
      "outcome": "failed",
      "hangup_cause": "LOSE_RACE"
    }
  }
}

call.ringing and call.answered use the same structure. On call.ringing, every state field is null. On call.answered, state.answered_at is set.

๐Ÿง 

Call analysis isn't a webhook event. There's no notification when a call's Call Analysis is ready. After call.ended, request the analysis and retry with backoff while it's still processing, as described in Processing Behavior.

Verifying Requests

Every request is signed so you can confirm it came from ZVMS Telecom and wasn't altered. The signature header holds an HMAC-SHA256 signature generated with your signing secret, and the timestamp header records when the request was sent.

โš ๏ธ

Keep your signing secret private. Store it on your server only, never in client-side code or public repositories. If you think it has been exposed, contact us right away so we can issue a new one.

Best Practices