TryVox

Webhook Subscriptions

Subscribe your URLs to TryVox events — call lifecycle, message delivery, custom notifications.

A webhook subscription registers a URL to receive HTTPS callbacks for one or more event_types. TryVox publishes events automatically and dispatches each event to every subscription whose events array matches.

call.ended is the event to subscribe to for call results. Unlike the per-call hangup_url, it does not require the number to be attached to a Voice Application — it is delivered for every call on the account that produces a CDR, including numbers delivered straight to your own SIP endpoint and numbers routed to an AI agent. Its payload is the same shape as the hangup webhook, so one handler serves both.

This is the right surface for hooking up your own infrastructure to TryVox events. For per-call webhooks (answer_url, hangup_url), use the inline URL fields on the call request — those are dispatched once per call, not per subscription.

Event occurs
event_type fired
TryVox
matches subscriptions
Webhook triggered
signed POST request
Your endpoint
receives payload
200 OK
delivery logged

Endpoints

MethodPathPurpose
POST/v1/notifications/webhooksCreate a subscription
GET/v1/notifications/webhooksList subscriptions
GET/v1/notifications/webhooks/{id}Get a subscription
PUT/v1/notifications/webhooks/{id}Update a subscription
DELETE/v1/notifications/webhooks/{id}Delete a subscription

Create

POST /v1/notifications/webhooks

Request body

FieldTypeRequiredDescription
urlstringyesHTTPS URL TryVox POSTs to. HTTP is rejected.
eventsstring[]yesEvent types to subscribe to. Use ["*"] for all events.
secretstringnoShared secret used to sign each delivery (X-TryVox-Signature header). If omitted, TryVox generates one and returns it once.
activebooleannoDefault: true. Inactive subscriptions are skipped during dispatch.
max_retriesintegernoRetries on non-2xx response. Default: 3.
timeout_msintegernoPer-attempt timeout. Default: 5000 (5 seconds).

Example

curl -X POST https://api.tryvox.io/v1/notifications/webhooks \
  -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/hooks/tryvox",
    "events": ["call.ended", "message.sent"],
    "max_retries": 5,
    "timeout_ms": 10000
  }'

Response

201 Created:

{
  "id": "8f7a4b2e-91c2-4f3d-9a1e-2c4b6d8f0a1c",
  "url": "https://your-app.com/hooks/tryvox",
  "events": ["call.ended", "message.sent"],
  "active": true,
  "max_retries": 5,
  "timeout_ms": 10000,
  "secret": "whsec_3b2c1a0d4e5f60718293a4b5c6d7e8f9",
  "created_at": "2026-04-09T10:30:00Z"
}

The secret is returned once at create time and never again. Store it. If you lose it, Update with a new secret.

List

GET /v1/notifications/webhooks

200 OK:

{
  "data": [
    {
      "id": "8f7a4b2e-91c2-4f3d-9a1e-2c4b6d8f0a1c",
      "url": "https://your-app.com/hooks/tryvox",
      "events": ["call.ended", "message.sent"],
      "active": true,
      "max_retries": 5,
      "timeout_ms": 10000,
      "created_at": "2026-04-09T10:30:00Z",
      "updated_at": "2026-04-09T10:30:00Z"
    }
  ]
}

secret is not returned on list / get — only on create / update.

Get

GET /v1/notifications/webhooks/{id}

404 NOT_FOUND if the subscription doesn't exist on this tenant.

Update

PUT /v1/notifications/webhooks/{id}

PATCH-style: only fields present in the body are applied. Include secret to rotate; the new value is returned in the response (the only time you'll see it).

curl -X PUT https://api.tryvox.io/v1/notifications/webhooks/$WEBHOOK_ID \
  -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
  -H "Content-Type: application/json" \
  -d '{"events": ["call.ended", "message.sent"], "active": true}'

200 OK with the updated subscription.

Delete

DELETE /v1/notifications/webhooks/{id}

204 No Content. Existing in-flight deliveries complete; no new dispatches.

Delivery format

Each delivery is a POST to your URL with:

Content-Type: application/json
X-TryVox-Event: call.ended
X-TryVox-Delivery: 1b2c3d4e-5f60-7180-9a2b-3c4d5e6f7a8b
X-TryVox-Timestamp: 1744194600
X-TryVox-Signature: t=1744194600,v1=<hex hmac-sha256>

Body:

{
  "event": "call.ended",
  "delivery_id": "1b2c3d4e-5f60-7180-9a2b-3c4d5e6f7a8b",
  "occurred_at": "2026-04-09T10:30:00Z",
  "data": { /* event-specific payload */ }
}

Verifying the signature

Compute HMAC-SHA256(secret, "{X-TryVox-Timestamp}.{raw-body}") and compare hex to the v1= value in X-TryVox-Signature. Reject deliveries whose timestamp is more than 5 minutes off — that defeats replay attacks.

import hmac, hashlib, time

def verify(secret, header_sig, header_ts, raw_body):
    if abs(time.time() - int(header_ts)) > 300:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{header_ts}.{raw_body}".encode(),
        hashlib.sha256
    ).hexdigest()
    sent = dict(p.split("=", 1) for p in header_sig.split(",")).get("v1")
    return hmac.compare_digest(expected, sent or "")

Retries

A delivery is retried when:

  • The response status is not 2xx.
  • The connection times out (timeout_ms).
  • The TLS handshake fails.

Retries use exponential backoff: 1s → 4s → 16s → 64s → 256s, capped at max_retries. After all retries are exhausted the delivery is marked failed and surfaces in the delivery log — TryVox does not keep retrying indefinitely.

Make your handler idempotent — duplicate deliveries can occur on retry edges and on the rare double-publish.

Event types

EventWhen
call.endedA call finished. Delivered for every call that produces a CDR, whatever the outcome — check Status in the payload to tell a completed call from a busy, unanswered, cancelled or failed one.
call.startedCall request accepted.
call.answeredCall connected.
message.receivedInbound message received.
message.sentMessage accepted by the carrier.
balance.lowAccount balance fell below the alert threshold.
account.updatedAccount details changed.

Use ["*"] to subscribe to all current and future events.

call.ended is the only call event currently dispatched. The others above are accepted on a subscription and reserved, but are not yet emitted — subscribe to them now and they will start arriving when they ship. Any event type not in this table is rejected with INVALID_EVENT_TYPE.

Errors

StatusCodeReason
400VALIDATION_FAILEDMissing field, url not HTTPS, events empty
401INVALID_CREDENTIALSAuth ID / Auth Token bad
404NOT_FOUNDSubscription not on this tenant

Notes

  • HTTPS only. HTTP URLs are rejected on create and update.
  • One subscription per URL is recommended. Multiple subscriptions to the same URL with overlapping event filters duplicate deliveries.
  • active: false is the right way to pause without losing the subscription. TryVox skips inactive subscriptions but keeps the configuration and delivery log.

On this page