Skip to main content

Webhooks API

There are two distinct webhook flows:

  • Tenant webhooks (outbound, us → you): you configure one webhook URL for your tenant and a list of event types. We POST events to that URL and sign each delivery with your webhook secret. See Webhook integration.
  • CPaaS callbacks (inbound, provider → us): the telephony provider posts call and SMS events to Relavoi. You do not call these endpoints; they are documented under CPaaS callbacks (inbound) so you understand the full picture.

:::info Single webhook URL per tenant Relavoi stores one webhook URL per tenant, not a collection of webhook subscriptions. Registering again overwrites the previous URL, event list, and secret. There are no per-webhook ids and no 409 conflict on duplicate URLs. :::

Endpoints​

MethodPathSummary
POST/v1/webhooksSet the tenant webhook URL + events
GET/v1/webhooksGet current webhook config + recent deliveries
POST/v1/webhooks/testEnqueue a synthetic session.created delivery
GET/v1/webhooks/logsPaginated delivery logs
POST/v1/webhooks/cpaas/voiceInbound CPaaS voice callback (returns XML)
POST/v1/webhooks/cpaas/smsInbound CPaaS SMS callback

Tenant endpoints require a Bearer JWT. Errors follow RFC 7807 Problem Details.

Event types​

The events you may subscribe to (the values the backend actually emits):

EventFires when
session.createdA masking session is created
session.activatedA session transitions to ACTIVE
session.expiredA session expires (grace period ends or hard timeout)
call.incomingAn inbound call arrives on a proxy for an active session
call.answeredA bridged call is answered
call.endedA call completes
call.failedA call attempt fails
sms.sentAn outbound (forwarded) SMS is sent
sms.receivedAn inbound SMS arrives on a proxy

POST /v1/webhooks​

Set (or replace) the tenant webhook URL and the events to deliver.

FieldTypeRequiredDescription
urlstringyesHTTPS endpoint URL
eventsstring[]yesOne or more event names from the table above (min 1)

Request

curl -X POST https://api.relavoi.com/v1/webhooks \
-H "Authorization: Bearer $RELAVOI_JWT" \
-H "Content-Type: application/json" \
-d '{
"url": "https://client.example.com/hook",
"events": ["session.created", "call.answered"]
}'

Response — 201 Created

{
"url": "https://client.example.com/hook",
"secret": "whsec_YOUR_WEBHOOK_SECRET",
"events": ["session.created", "call.answered"]
}
danger

The secret (prefix whsec_) is the HMAC key used to sign deliveries to your URL. It is returned each time you register; store it and verify the signature on every delivery.


GET /v1/webhooks​

Returns the current webhook configuration plus recent delivery attempts.

Request

curl https://api.relavoi.com/v1/webhooks \
-H "Authorization: Bearer $RELAVOI_JWT"

Response

{
"url": "https://client.example.com/hook",
"events": [],
"hasSecret": true,
"recentDeliveries": []
}
  • url — the configured webhook URL, or null if none is set.
  • events — the subscribed event names.
  • hasSecret — whether a signing secret is configured (the secret itself is never returned here).
  • recentDeliveries — up to the latest 50 delivery-log entries (same shape as /v1/webhooks/logs).

POST /v1/webhooks/test​

Enqueues a synthetic session.created delivery to your configured webhook URL. Useful for verifying your signature-verification implementation. Takes no request body — the event type is always session.created.

Request

curl -X POST https://api.relavoi.com/v1/webhooks/test \
-H "Authorization: Bearer $RELAVOI_JWT"

Response — 202 Accepted

{
"enqueued": true,
"event": {
"id": "evt_test_3e2b77ae54cdbd9a",
"type": "session.created",
"createdAt": "2026-07-15T12:15:35.673Z",
"data": {
"sessionId": "sess_test_cfe25d636a9a",
"tenantId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"proxyNumber": "+2348000000000",
"test": true
}
}
}

If no webhook URL is configured, this returns 400 with a no-webhook problem detail.


GET /v1/webhooks/logs​

Paginated delivery logs, newest first. Cursor pagination by delivery timestamp.

QueryTypeRequiredDescription
limitintegernoDefault 50, max 200
afterstring (ISO 8601)noCursor — timestamp of the last row from the previous page

Request

curl "https://api.relavoi.com/v1/webhooks/logs?limit=20" \
-H "Authorization: Bearer $RELAVOI_JWT"

Response

{
"data": [
{
"id": "9c1f2b3a-4d5e-6f70-8a9b-0c1d2e3f4a5b",
"event": "call.answered",
"url": "https://client.example.com/hook",
"statusCode": 200,
"success": true,
"attemptCount": 1,
"error": null,
"requestedAt": "2026-07-15T12:33:24.000Z",
"completedAt": "2026-07-15T12:33:24.142Z"
}
],
"pagination": {
"count": 1,
"after": null
}
}

Delivery-log fields: event, url, statusCode, success, attemptCount, requestedAt, completedAt (plus id and an error message when a delivery failed).


CPaaS callbacks (inbound)​

These endpoints are called by the telephony provider, not by tenants. They are listed here so you understand what Relavoi receives on your behalf. In production they are verified via an HMAC signature header; the signature check is skipped only in the provider sandbox.

POST /v1/webhooks/cpaas/voice​

The telephony provider posts inbound call events here (form-encoded). Relavoi makes the routing decision and responds with provider voice XML (<Say>, <Dial>, <Reject>, <Redirect>, <Play>) telling the provider how to handle the call. For an expired session with DEAD_LINE behavior, for example, the response is:

<?xml version="1.0" encoding="UTF-8"?>
<Response><Say voice="woman">This number is no longer in service</Say><Hangup/></Response>

An active session with recording enabled returns a consent <Say>/<Play> followed by a <Dial> to the other party. If signature verification fails (non-sandbox), the endpoint responds 403 with an empty voice response.

POST /v1/webhooks/cpaas/sms​

The telephony provider posts inbound SMS here (form-encoded). Relavoi matches the message to an active session and forwards it to the other party. The endpoint returns a small JSON acknowledgement:

{ "ok": true }

Errors (tenant endpoints)

Errors follow RFC 7807 Problem Details (application/problem+json):

{
"type": "https://api.relavoi.com/errors/validation",
"title": "Bad Request",
"status": 400,
"detail": "url: Invalid url"
}
StatusType slugWhen
400validationInvalid URL or empty events array
400no-webhookPOST /webhooks/test with no URL configured
401unauthorizedMissing or invalid JWT
404not-foundTenant not found