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
Field Type Required Description urlstring Yes HTTPS endpoint URL (HTTP is rejected) test_urlstring No Separate URL for test-environment webhook events event_typesarray Yes One or more event types to subscribe to
Available event types
Event type Category 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
Field Type Default Description event_typestring "call.ended"Event type to simulate sample_payloadobject null Custom 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
Parameter Type Default Description limitinteger 100Max 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
}
]
Field Type Description statusstring "pending", "delivered", "failed", or "dead"attemptinteger Delivery attempt number (1 = first try) next_retry_atstring | null When the next retry is scheduled (null if delivered or dead)
Subscription response fields
Field Type Description idstring Subscription UUID tenant_idstring Owning tenant urlstring Delivery endpoint test_urlstring | null Test environment endpoint event_typesarray Subscribed event types is_activeboolean Whether the subscription is active created_atstring ISO 8601 last_success_atstring | null Last successful delivery last_failure_atstring | null Last failed delivery consecutive_failuresinteger Current failure streak disabled_atstring | null When auto-disabled (null if active) disabled_reasonstring | null Why it was disabled (e.g., "auto_disabled_failure_threshold")