Webhook endpoints
Register HTTPS endpoints for event notifications, rotate signing secrets, send test pings and inspect delivery history.
A webhook endpoint is an HTTPS URL Ody POSTs signed events to. This page covers managing endpoints. For what gets delivered and how to verify it, see Webhooks, Event types and Verifying signatures.
Endpoints are managed only through this API. There's no webhook screen in the app and no MCP tool for it yet.
#The webhook endpoint object
iduuidurlstring — HTTPS only.eventsarray of strings — Subscribed event types, or["*"]for every current and future type.labelstring | null — Your own name for it.status"enabled" | "disabled"apiVersionstring | null — The payload version pinned when the endpoint was created.createdAttimestampsecretstring —whsec_…signing secret. Returned only by create and rotate. Store it right away.
A workspace can have up to 50 endpoints.
#List event types
/v1/api/webhook-eventsReturns the catalog of event types you can subscribe to.
Scope: webhooks:manage
Returns { events: string[] }.
curl https://api.ody.co/v1/api/webhook-events \
-H "Authorization: Bearer $ODY_API_KEY"const types = await ody.webhooks.eventTypes();types = client.webhooks.event_types()#List endpoints
/v1/api/webhooksSecrets are never included.
Scope: webhooks:manage
Returns { webhooks: WebhookEndpoint[] }.
curl https://api.ody.co/v1/api/webhooks \
-H "Authorization: Bearer $ODY_API_KEY"const endpoints = await ody.webhooks.list();endpoints = client.webhooks.list()#Create an endpoint
/v1/api/webhooksScope: webhooks:manage
Body
urlstring required — Public HTTPS URL.eventsarray of strings required — Event types, or["*"].labelstring
Returns 201 with { webhook }, including secret. Returns 400 for a non-HTTPS URL, an unknown event type, or when you'd exceed 50 endpoints.
curl https://api.ody.co/v1/api/webhooks \
-H "Authorization: Bearer $ODY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/ody/webhooks",
"events": ["message.received", "call.completed", "call.summary.completed"],
"label": "Production CRM sync"
}'const endpoint = await ody.webhooks.create({
url: "https://example.com/ody/webhooks",
events: ["message.received", "call.completed", "call.summary.completed"],
label: "Production CRM sync",
});
console.log(endpoint.secret); // whsec_… store it nowendpoint = client.webhooks.create(
url="https://example.com/ody/webhooks",
events=["message.received", "call.completed", "call.summary.completed"],
label="Production CRM sync",
){
"webhook": {
"id": "3a8f…",
"url": "https://example.com/ody/webhooks",
"events": ["message.received", "call.completed", "call.summary.completed"],
"label": "Production CRM sync",
"status": "enabled",
"apiVersion": "2026-06-01",
"createdAt": "2026-10-14T18:20:00.000Z",
"secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
}#Retrieve an endpoint
/v1/api/webhooks/:idScope: webhooks:manage
Returns { webhook } (without the secret).
curl https://api.ody.co/v1/api/webhooks/3a8f… \
-H "Authorization: Bearer $ODY_API_KEY"const endpoint = await ody.webhooks.get("3a8f…");endpoint = client.webhooks.get("3a8f…")#Update an endpoint
/v1/api/webhooks/:idChange the URL, subscriptions or label, or pause deliveries with status: "disabled".
Scope: webhooks:manage
Body: any of url, events, label (null clears it), status.
Returns { webhook }.
curl -X PATCH https://api.ody.co/v1/api/webhooks/3a8f… \
-H "Authorization: Bearer $ODY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"disabled"}'await ody.webhooks.update("3a8f…", { status: "disabled" });client.webhooks.update("3a8f…", status="disabled")curl -X DELETE https://api.ody.co/v1/api/webhooks/3a8f… \
-H "Authorization: Bearer $ODY_API_KEY"await ody.webhooks.delete("3a8f…");client.webhooks.delete("3a8f…")#Rotate the signing secret
/v1/api/webhooks/:id/rotateIssues a new whsec_ secret and returns it once. Signatures made with the old secret stop validating immediately. Deploy the new secret to your verifier first, or accept both secrets briefly (see Verifying signatures).
Scope: webhooks:manage
Returns { webhook }, including the new secret.
curl -X POST https://api.ody.co/v1/api/webhooks/3a8f…/rotate \
-H "Authorization: Bearer $ODY_API_KEY"const { secret } = await ody.webhooks.rotate("3a8f…");secret = client.webhooks.rotate("3a8f…")["secret"]#Send a test delivery
/v1/api/webhooks/:id/testPOSTs a ping event to the endpoint, signed exactly like a real delivery, and returns the result straight away. This is the fastest way to check your signature verification.
Scope: webhooks:manage
Returns { delivered: boolean, attempt: { at, statusCode, error?, durationMs } }. delivered is true for any 2xx response.
curl -X POST https://api.ody.co/v1/api/webhooks/3a8f…/test \
-H "Authorization: Bearer $ODY_API_KEY"const { delivered, attempt } = await ody.webhooks.test("3a8f…");res = client.webhooks.test("3a8f…"){
"delivered": true,
"attempt": { "at": "2026-10-14T18:21:03.114Z", "statusCode": 200, "durationMs": 143 }
}#List deliveries
/v1/api/webhooks/:id/deliveriesRecent deliveries, newest first, with every attempt's status code or error. This is your debugging view.
Scope: webhooks:manage
Query parameters
limitinteger — 1–200, default 50.
Returns { deliveries: [{ id, webhookId, eventId, status, attempts, createdAt }] }. status is pending, success or failed.
curl "https://api.ody.co/v1/api/webhooks/3a8f…/deliveries?limit=20" \
-H "Authorization: Bearer $ODY_API_KEY"const deliveries = await ody.webhooks.deliveries("3a8f…", { limit: 20 });deliveries = client.webhooks.deliveries("3a8f…", limit=20)