TVH Voice docs

#Event catalog

Every event has the same envelope; data depends on the type. Webhooks deliver it as the request body (signed, see webhooks.md), the live stream as data: lines, GET /v1/events as list items.

{
  "id": "uuid — equals the webhook-id header",
  "type": "call.answered",
  "version": 1,
  "created_at": "ISO 8601",
  "data": {}
}

JSON Schemas (draft 2020-12) for each envelope: docs/schemas/events/<type>.json, and the webhooks section of openapi.yaml.

Type When
call.initiated An outbound or internal call was created (click-to-call or dialed from a phone).
call.incoming A call arrived on one of your numbers.
call.ringing The destination is ringing (outbound: the called party; inbound: an agent)..
call.answered The destination answered; both parties are connected..
call.updated The call was put on hold or resumed..
call.ended The call is over.
recording.ready A call recording or voicemail is stored.
agent.registered An agent went from no registered device to at least one (softphone online)..
agent.unregistered The agent's last registered device went away (unregistered or expired)..
number.order.updated A number order changed status.
number.requirements.updated A requirement group changed status (submitted, in_review, approved, rejected) or items were accepted/rejected..
carrier.health.changed A carrier's SIP connectivity changed (registration lost, trunk unreachable, recovered)..
usage.alert A spend-guard threshold was crossed (alert level or hard stop).
webhook.test Sent by POST /v1/webhook-endpoints/{id}/test to check your endpoint and signature verification..

#call.initiated

An outbound or internal call was created (click-to-call or dialed from a phone). For click-to-call the agent phone rings next.

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "call.initiated",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "call_id": "0b8e6c1a-2f3d-4e5a-8b9c-1d2e3f4a5b6c",
    "direction": "outbound",
    "origin": "api",
    "status": "initiated",
    "agent_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
    "agent_ref": "user-123",
    "from": "+31201234567",
    "to": "+31612345678",
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "caller_id_number_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d",
    "initiated_at": "2026-11-09T10:15:00.000Z",
    "ringing_at": null,
    "answered_at": null,
    "ended_at": null,
    "duration_seconds": null,
    "billable_seconds": null,
    "hangup_cause": null,
    "hangup_by": null,
    "recording": null,
    "recording_enabled": false,
    "external_ref": "crm-activity-42",
    "metadata": {
      "lead_id": "L-1001"
    }
  }
}

#call.incoming

A call arrived on one of your numbers. Webhook-decides inbound rules send this synchronously and wait up to 2 s for a route (see the inbound routing guide).

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "call.incoming",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "call_id": "0b8e6c1a-2f3d-4e5a-8b9c-1d2e3f4a5b6c",
    "direction": "inbound",
    "origin": "carrier",
    "status": "initiated",
    "agent_id": null,
    "agent_ref": null,
    "from": "+31612345678",
    "to": "+31201234567",
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "caller_id_number_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d",
    "initiated_at": "2026-11-09T10:15:00.000Z",
    "ringing_at": null,
    "answered_at": null,
    "ended_at": null,
    "duration_seconds": null,
    "billable_seconds": null,
    "hangup_cause": null,
    "hangup_by": null,
    "recording": null,
    "recording_enabled": false,
    "external_ref": "crm-activity-42",
    "metadata": {
      "lead_id": "L-1001"
    }
  }
}

#call.ringing

The destination is ringing (outbound: the called party; inbound: an agent).

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "call.ringing",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "call_id": "0b8e6c1a-2f3d-4e5a-8b9c-1d2e3f4a5b6c",
    "direction": "outbound",
    "origin": "api",
    "status": "ringing",
    "agent_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
    "agent_ref": "user-123",
    "from": "+31201234567",
    "to": "+31612345678",
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "caller_id_number_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d",
    "initiated_at": "2026-11-09T10:15:00.000Z",
    "ringing_at": null,
    "answered_at": null,
    "ended_at": null,
    "duration_seconds": null,
    "billable_seconds": null,
    "hangup_cause": null,
    "hangup_by": null,
    "recording": null,
    "recording_enabled": false,
    "external_ref": "crm-activity-42",
    "metadata": {
      "lead_id": "L-1001"
    }
  }
}

#call.answered

The destination answered; both parties are connected.

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "call.answered",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "call_id": "0b8e6c1a-2f3d-4e5a-8b9c-1d2e3f4a5b6c",
    "direction": "outbound",
    "origin": "api",
    "status": "answered",
    "agent_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
    "agent_ref": "user-123",
    "from": "+31201234567",
    "to": "+31612345678",
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "caller_id_number_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d",
    "initiated_at": "2026-11-09T10:15:00.000Z",
    "ringing_at": null,
    "answered_at": null,
    "ended_at": null,
    "duration_seconds": null,
    "billable_seconds": null,
    "hangup_cause": null,
    "hangup_by": null,
    "recording": null,
    "recording_enabled": false,
    "external_ref": "crm-activity-42",
    "metadata": {
      "lead_id": "L-1001"
    }
  }
}

#call.updated

The call was put on hold or resumed.

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "call.updated",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "call_id": "0b8e6c1a-2f3d-4e5a-8b9c-1d2e3f4a5b6c",
    "direction": "outbound",
    "origin": "api",
    "status": "answered",
    "agent_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
    "agent_ref": "user-123",
    "from": "+31201234567",
    "to": "+31612345678",
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "caller_id_number_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d",
    "initiated_at": "2026-11-09T10:15:00.000Z",
    "ringing_at": null,
    "answered_at": null,
    "ended_at": null,
    "duration_seconds": null,
    "billable_seconds": null,
    "hangup_cause": null,
    "hangup_by": null,
    "recording": null,
    "recording_enabled": false,
    "external_ref": "crm-activity-42",
    "metadata": {
      "lead_id": "L-1001"
    },
    "change": "held"
  }
}

#call.ended

The call is over. Contains the final status, durations and hangup cause (the CDR).

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "call.ended",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "call_id": "0b8e6c1a-2f3d-4e5a-8b9c-1d2e3f4a5b6c",
    "direction": "outbound",
    "origin": "api",
    "status": "completed",
    "agent_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
    "agent_ref": "user-123",
    "from": "+31201234567",
    "to": "+31612345678",
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "caller_id_number_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d",
    "initiated_at": "2026-11-09T10:15:00.000Z",
    "ringing_at": null,
    "answered_at": null,
    "ended_at": "2026-11-09T10:17:30.000Z",
    "duration_seconds": 150,
    "billable_seconds": 141,
    "hangup_cause": "NORMAL_CLEARING",
    "hangup_by": "remote",
    "recording": null,
    "recording_enabled": false,
    "external_ref": "crm-activity-42",
    "metadata": {
      "lead_id": "L-1001"
    }
  }
}

#recording.ready

A call recording or voicemail is stored. Fetch a playback URL with GET /v1/calls/{call_id}/recording.

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "recording.ready",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "recording_id": "9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a",
    "call_id": "0b8e6c1a-2f3d-4e5a-8b9c-1d2e3f4a5b6c",
    "kind": "call",
    "duration_seconds": 141,
    "size_bytes": 27072044,
    "from": "+31201234567",
    "to": "+31612345678"
  }
}

#agent.registered

An agent went from no registered device to at least one (softphone online).

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "agent.registered",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "agent_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
    "agent_ref": "user-123",
    "user_agent": "SIP.js/0.21.2",
    "network_ip": "203.0.113.7",
    "transport": "ws"
  }
}

#agent.unregistered

The agent's last registered device went away (unregistered or expired).

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "agent.unregistered",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "agent_id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d",
    "agent_ref": "user-123"
  }
}

#number.order.updated

A number order changed status. completed means the numbers are active and usable.

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "number.order.updated",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "order_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d",
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "status": "completed",
    "requirement_group_id": null,
    "customer_reference": "crm-42",
    "failure_reason": null,
    "numbers": [
      {
        "e164": "+31201234567",
        "status": "active",
        "number_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"
      }
    ]
  }
}

#number.requirements.updated

A requirement group changed status (submitted, in_review, approved, rejected) or items were accepted/rejected.

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "number.requirements.updated",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "requirement_group_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d",
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "country": "NL",
    "number_type": "local",
    "status": "approved",
    "rejection_reason": null,
    "items": [
      {
        "key": "business_name",
        "status": "accepted",
        "rejection_reason": null
      }
    ]
  }
}

#carrier.health.changed

A carrier's SIP connectivity changed (registration lost, trunk unreachable, recovered).

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "carrier.health.changed",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "carrier_id": "c4a1e7b2-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
    "name": "Telnyx production",
    "state": "down",
    "previous_state": "healthy",
    "detail": "Registration failed"
  }
}

#usage.alert

A spend-guard threshold was crossed (alert level or hard stop). Outbound calls are refused after a hard stop until an admin resets it.

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "usage.alert",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "kind": "outbound_minutes_alert",
    "value": 1512,
    "threshold": 1500
  }
}

#webhook.test

Sent by POST /v1/webhook-endpoints/{id}/test to check your endpoint and signature verification.

{
  "id": "4c1f8e2a-7b3d-4e9f-a1c2-5d6e7f8a9b0c",
  "type": "webhook.test",
  "version": 1,
  "created_at": "2026-11-09T10:15:00.000Z",
  "data": {
    "endpoint_id": "6f1c2b9e-3d4a-4f5b-9c8d-7e6f5a4b3c2d"
  }
}