TryVox

Webhook Subscriptions

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

Webhook Subscriptions

A webhook subscription registers a URL to receive HTTPS callbacks for one or more event_types. TryVox publishes events automatically (call lifecycle, message delivery, recording completion, custom notifications you fire via Send notification) and dispatches each event to every subscription whose events array matches.

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.

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.completed", "message.delivered", "recording.completed"],
    "max_retries": 5,
    "timeout_ms": 10000
  }'

Response

201 Created:

{
  "id": "8f7a4b2e-91c2-4f3d-9a1e-2c4b6d8f0a1c",
  "url": "https://your-app.com/hooks/tryvox",
  "events": ["call.completed", "message.delivered", "recording.completed"],
  "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.completed", "message.delivered"],
      "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.completed", "call.failed", "message.delivered"], "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.completed
X-TryVox-Delivery: 1b2c3d4e-5f60-7180-9a2b-3c4d5e6f7a8b
X-TryVox-Timestamp: 1744194600
X-TryVox-Signature: t=1744194600,v1=<hex hmac-sha256>

Body:

{
  "event": "call.completed",
  "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.queuedOutbound call request accepted.
call.ringingCall is ringing the destination.
call.answeredCall connected.
call.completedCall ended normally.
call.failedCall failed before answer.
message.queuedMessage accepted by TryVox.
message.sentAccepted by Meta / SMS carrier.
message.deliveredRecipient handset received it.
message.readRecipient opened it (only with read receipts on).
message.failedDelivery failed permanently.
recording.completedRecording finalised and uploaded.
transcription.completedSpeech-to-text finished.
conversation.openedNew conversation created.
<custom>Anything you publish via Send notification.

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

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