Skip to main content
Webhook subscriptions can be managed via the dashboard UI or programmatically via these endpoints. Both use Supabase JWT authentication (not API keys).
These endpoints use dashboard (JWT) authentication, not the /api/v1/* API key surface. Your Supabase session must have the webhooks:manage permission.

Create a subscription

curl -X POST https://api.yotel.in/api/webhooks \
  -H "Authorization: Bearer SUPABASE_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/yotel",
    "event_types": ["call.ended", "call.recording_ready", "campaign.completed"]
  }'
Auth: Supabase JWT + webhooks:manage  |  Status: 201 Created

Request body

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint URL (HTTP is rejected)
test_urlstringNoSeparate URL for test-environment webhook events
event_typesarrayYesOne or more event types to subscribe to

Available event types

Event typeCategory
call.startedCall
call.answeredCall
call.endedCall
call.recording_readyCall
call.transcript_readyCall
ai_session.startedAI Session
ai_session.endedAI Session
ai_session.transferredAI Session
ai_session.escalatedAI Session
ai_session.conference_changedAI Session
lead.completedLead
campaign.startedCampaign
campaign.pausedCampaign
campaign.resumedCampaign
campaign.completedCampaign
agent.logged_inAgent
agent.logged_outAgent

Response

The response includes a signing_secret field that is shown only once. Store it securely — you’ll need it to verify webhook signatures.
{
  "id": "wh_sub_abc123",
  "tenant_id": "tenant_xyz",
  "url": "https://hooks.example.com/yotel",
  "test_url": null,
  "event_types": ["call.ended", "call.recording_ready"],
  "is_active": true,
  "signing_secret": "whsec_abc123...",
  "created_at": "2026-05-18T10:00:00Z",
  "consecutive_failures": 0
}
Test tenants can only point webhooks at localhost, 127.0.0.1, *.ngrok.io, or *.test.yotel.in. Production URLs are rejected for test tenants.

List subscriptions

curl https://api.yotel.in/api/webhooks \
  -H "Authorization: Bearer SUPABASE_JWT"
Auth: Supabase JWT + webhooks:manage  |  Status: 200 OK Query parameter: include_disabled (boolean, default false) — set to true to include auto-disabled subscriptions. The signing_secret field is not returned on list/get — only on create and rotate.

Delete a subscription

curl -X DELETE https://api.yotel.in/api/webhooks/{subscription_id} \
  -H "Authorization: Bearer SUPABASE_JWT"
Auth: Supabase JWT + webhooks:manage  |  Status: 204 No Content

Rotate signing secret

Issues a new signing secret for an existing subscription. A 24-hour grace window accepts signatures from both the old and new secret, giving you time to deploy the new secret without downtime.
curl -X POST https://api.yotel.in/api/webhooks/{subscription_id}/rotate \
  -H "Authorization: Bearer SUPABASE_JWT"
Auth: Supabase JWT + webhooks:manage  |  Status: 200 OK The response includes the new signing_secret (shown once). During the grace window, the X-Yotel-Signature header contains both signatures: sha256=OLD,sha256=NEW — your verification code should accept either.

Test a subscription

Sends a synchronous test event to your endpoint and reports the result immediately. This is a dry-run — it bypasses the delivery log.
curl -X POST https://api.yotel.in/api/webhooks/{subscription_id}/test \
  -H "Authorization: Bearer SUPABASE_JWT" \
  -H "Content-Type: application/json" \
  -d '{"event_type": "call.ended"}'
Auth: Supabase JWT + webhooks:manage  |  Status: 200 OK

Request body

FieldTypeDefaultDescription
event_typestring"call.ended"Event type to simulate
sample_payloadobjectnullCustom payload (uses a default sample if omitted)

Response

{
  "delivered": true,
  "http_status": 200,
  "response_snippet": "{\"ok\":true}",
  "duration_ms": 142,
  "error": null
}

View delivery log

Inspect recent deliveries for a subscription — status, response codes, timing, and retry schedule.
curl "https://api.yotel.in/api/webhooks/{subscription_id}/deliveries?limit=50" \
  -H "Authorization: Bearer SUPABASE_JWT"
Auth: Supabase JWT + webhooks:manage  |  Status: 200 OK

Query parameters

ParameterTypeDefaultDescription
limitinteger100Max rows to return (1–1000)

Response

[
  {
    "id": "del_abc",
    "subscription_id": "wh_sub_abc123",
    "event_type": "call.ended",
    "event_id": "evt_xyz",
    "payload": { "...": "..." },
    "attempt": 1,
    "status": "delivered",
    "http_status": 200,
    "response_snippet": "{\"ok\":true}",
    "created_at": "2026-05-18T10:04:48Z",
    "delivered_at": "2026-05-18T10:04:48Z",
    "next_retry_at": null,
    "duration_ms": 87
  }
]
FieldTypeDescription
statusstring"pending", "delivered", "failed", or "dead"
attemptintegerDelivery attempt number (1 = first try)
next_retry_atstring | nullWhen the next retry is scheduled (null if delivered or dead)

Subscription response fields

FieldTypeDescription
idstringSubscription UUID
tenant_idstringOwning tenant
urlstringDelivery endpoint
test_urlstring | nullTest environment endpoint
event_typesarraySubscribed event types
is_activebooleanWhether the subscription is active
created_atstringISO 8601
last_success_atstring | nullLast successful delivery
last_failure_atstring | nullLast failed delivery
consecutive_failuresintegerCurrent failure streak
disabled_atstring | nullWhen auto-disabled (null if active)
disabled_reasonstring | nullWhy it was disabled (e.g., "auto_disabled_failure_threshold")