Webhooks
A webhook endpoint is a URL that receives signed events from your account. Each account and subaccount has its own endpoints. This resource manages endpoints, rotates signing secrets, lists deliveries and resends events. Every event type is listed in the Webhooks and events guide.
Guide: Webhooks and events.
Endpoints
| POST | /v1/webhook_endpoints | Create an endpoint |
| GET | /v1/webhook_endpoints | List endpoints |
| GET | /v1/webhook_endpoints/{id} | Retrieve an endpoint |
| PATCH | /v1/webhook_endpoints/{id} | Update an endpoint |
| DELETE | /v1/webhook_endpoints/{id} | Delete an endpoint |
| POST | /v1/webhook_endpoints/{id}/rotate_secret | Rotate the secret |
| GET | /v1/webhook_endpoints/{id}/deliveries | List deliveries |
| POST | /v1/events/{id}/resend | Resend an event |
| GET | /v1/events | List events |
| GET | /v1/events/{id} | Retrieve an event |
The event object
| Field | Type | Description |
|---|---|---|
id | string | Unique ID, starts with evt_. The webhook-id header carries the same ID with the msg_ prefix and stays the same on every retry. |
type | string | The event type, such as call.bridged. See the event reference in the Webhooks and events guide. |
created_at | string | When the event happened, ISO 8601 with milliseconds. |
livemode | boolean | false for events from test keys. |
account_id | string | The account or subaccount the event belongs to. |
data | object | The key fields for the event type plus the current state of the object. |
Create an endpoint
Adds a URL that receives events. The response includes the signing secret. It is shown once.
POST/v1/webhook_endpoints
| Body parameters | Type | Description |
|---|---|---|
urlrequired | string | An HTTPS URL on your server. |
events | string[] | Event types to send. Leave out to receive every type. |
description | string | A label you will recognize. |
curl https://api.liveleg.dev/v1/webhook_endpoints \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/hooks/liveleg",
"description": "CRM sync",
"events": [
"call.bridged",
"call.completed",
"amd.completed",
"summary.ready"
]
}'const res = await fetch('https://api.liveleg.dev/v1/webhook_endpoints', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: "https://app.example.com/hooks/liveleg",
description: "CRM sync",
events: [
"call.bridged",
"call.completed",
"amd.completed",
"summary.ready"
]
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/webhook_endpoints",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"url": "https://app.example.com/hooks/liveleg",
"description": "CRM sync",
"events": ["call.bridged", "call.completed", "amd.completed", "summary.ready"],
},
).json() {
"id": "whe_7Kc02",
"object": "webhook_endpoint",
"url": "https://app.example.com/hooks/liveleg",
"description": "CRM sync",
"events": [
"call.bridged",
"call.completed",
"amd.completed",
"summary.ready"
],
"status": "enabled",
"account_id": "acc_ridgeline",
"livemode": true,
"created_at": "2026-11-02T17:31:09Z",
"secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
} List endpoints
Returns the endpoints of the account, newest first. Secrets are not included.
GET/v1/webhook_endpoints
| Query parameters | Type | Description |
|---|---|---|
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/webhook_endpoints?limit=20" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/webhook_endpoints?limit=20', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/webhook_endpoints",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"limit": 20,
},
).json() {
"object": "list",
"data": [
{
"id": "whe_7Kc02",
"object": "webhook_endpoint",
"url": "https://app.example.com/hooks/liveleg",
"description": "CRM sync",
"events": [
"call.bridged",
"call.completed",
"amd.completed",
"summary.ready"
],
"status": "enabled",
"account_id": "acc_ridgeline",
"livemode": true,
"created_at": "2026-11-02T17:31:09Z"
}
],
"has_more": false
} Retrieve an endpoint
Returns one endpoint. The secret is not included.
GET/v1/webhook_endpoints/{id}
curl https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "whe_7Kc02",
"object": "webhook_endpoint",
"url": "https://app.example.com/hooks/liveleg",
"description": "CRM sync",
"events": [
"call.bridged",
"call.completed",
"amd.completed",
"summary.ready"
],
"status": "enabled",
"account_id": "acc_ridgeline",
"livemode": true,
"created_at": "2026-11-02T17:31:09Z"
} Update an endpoint
Changes the URL, the event types or the status. A disabled endpoint receives nothing until you enable it again. Events from that time are not sent automatically. They stay in the event log for 30 days and can be resent.
PATCH/v1/webhook_endpoints/{id}
| Body parameters | Type | Description |
|---|---|---|
url | string | A new HTTPS URL. |
events | string[] | Replaces the list of event types. |
description | string | A label you will recognize. |
status | enabled | disabled | Turns delivery on or off. |
curl -X PATCH https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02 \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
"call.bridged",
"call.completed",
"amd.completed",
"summary.ready",
"recording.ready"
]
}'const res = await fetch('https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
events: [
"call.bridged",
"call.completed",
"amd.completed",
"summary.ready",
"recording.ready"
]
}),
});
const data = await res.json();import os, requests
data = requests.patch(
"https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"events": ["call.bridged", "call.completed", "amd.completed", "summary.ready", "recording.ready"],
},
).json() {
"id": "whe_7Kc02",
"object": "webhook_endpoint",
"url": "https://app.example.com/hooks/liveleg",
"description": "CRM sync",
"events": [
"call.bridged",
"call.completed",
"amd.completed",
"summary.ready",
"recording.ready"
],
"status": "enabled",
"account_id": "acc_ridgeline",
"livemode": true,
"created_at": "2026-11-02T17:31:09Z"
} Delete an endpoint
Deletes the endpoint. Deliveries waiting for a retry are canceled.
DELETE/v1/webhook_endpoints/{id}
curl -X DELETE https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.delete(
"https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "whe_7Kc02",
"object": "webhook_endpoint",
"deleted": true
} Rotate the secret
Issues a new signing secret. Until the old one expires, each delivery carries two signatures in webhook-signature, one per secret. Update your server, then let the old secret expire.
POST/v1/webhook_endpoints/{id}/rotate_secret
| Body parameters | Type | Description |
|---|---|---|
expire_old_in_hours | integer | 0 to 168. 0 ends the old secret right away. Default 24. |
curl https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02/rotate_secret \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expire_old_in_hours": 24
}'const res = await fetch('https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02/rotate_secret', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
expire_old_in_hours: 24
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02/rotate_secret",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"expire_old_in_hours": 24,
},
).json() {
"id": "whe_7Kc02",
"object": "webhook_endpoint",
"secret": "whsec_Qd7tYb2NwL0xPcR5sVh8KmZ1eA3uJ6gF",
"previous_secret_expires_at": "2026-11-04T09:12:44Z"
} List deliveries
Returns the delivery log for an endpoint, newest first, with the response your server gave. A delivery that times out after 15 seconds or gets a response other than 2xx is retried after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours and 10 hours, then every 12 hours until about 3 days have passed. Logs are kept for 30 days.
GET/v1/webhook_endpoints/{id}/deliveries
| Query parameters | Type | Description |
|---|---|---|
status | string | succeeded, failed or pending. pending means a retry is scheduled. |
event_id | string | Only deliveries of this event. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02/deliveries?status=pending" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02/deliveries?status=pending', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/webhook_endpoints/whe_7Kc02/deliveries",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"status": "pending",
},
).json() {
"object": "list",
"data": [
{
"id": "dlv_6Hs2Lm4",
"object": "webhook_delivery",
"endpoint_id": "whe_7Kc02",
"event_id": "evt_2Wc9Tb7",
"event_type": "call.completed",
"webhook_id": "msg_2Wc9Tb7",
"status": "pending",
"attempts": 2,
"response_status": 503,
"response_ms": 212,
"next_attempt_at": "2026-11-03T16:22:31Z",
"created_at": "2026-11-03T15:47:26.118Z"
}
],
"has_more": false
} Resend an event
Sends an event again, with the same ID and the same webhook-id. Without endpoint_id it goes to every enabled endpoint that receives its type. Events can be resent for 30 days.
POST/v1/events/{id}/resend
| Body parameters | Type | Description |
|---|---|---|
endpoint_id | string | Send to this endpoint only. |
curl https://api.liveleg.dev/v1/events/evt_2Wc81Lk0/resend \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"endpoint_id": "whe_7Kc02"
}'const res = await fetch('https://api.liveleg.dev/v1/events/evt_2Wc81Lk0/resend', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
endpoint_id: "whe_7Kc02"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/events/evt_2Wc81Lk0/resend",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"endpoint_id": "whe_7Kc02",
},
).json() {
"object": "list",
"data": [
{
"id": "dlv_6Ht01Rc",
"object": "webhook_delivery",
"endpoint_id": "whe_7Kc02",
"event_id": "evt_2Wc81Lk0",
"event_type": "call.bridged",
"webhook_id": "msg_2Wc81Lk0",
"status": "pending",
"attempts": 0,
"response_status": null,
"response_ms": null,
"next_attempt_at": null,
"created_at": "2026-11-03T16:02:40.551Z"
}
],
"has_more": false
} List events
Returns events from the last 30 days, newest first. Use it to catch up after your endpoint was down.
GET/v1/events
| Query parameters | Type | Description |
|---|---|---|
type | string | One event type, or a group such as call.*. |
created_after | string | ISO 8601. Only events after this time. |
limit | integer | 1 to 100. Default 20. |
starting_after | string | The last ID from the previous page. |
curl "https://api.liveleg.dev/v1/events?type=call.*&created_after=2026-11-03T15%3A00%3A00Z" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/events?type=call.*&created_after=2026-11-03T15%3A00%3A00Z', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/events",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"type": "call.*",
"created_after": "2026-11-03T15:00:00Z",
},
).json() {
"object": "list",
"data": [
{
"id": "evt_2Wc81Lk0",
"type": "call.bridged",
"created_at": "2026-11-03T15:42:07.412Z",
"livemode": true,
"account_id": "acc_ridgeline",
"data": {
"call_id": "cal_8Jq2mT4x",
"dial_session_id": "dls_31Ka9",
"agent_id": "agt_114",
"speech_ended_to_bridge_ms": 980,
"legs_canceled": 2
}
}
],
"has_more": true
} Retrieve an event
Returns one event with the same envelope as the webhook delivery.
GET/v1/events/{id}
curl https://api.liveleg.dev/v1/events/evt_2Wc81Lk0 \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/events/evt_2Wc81Lk0', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/events/evt_2Wc81Lk0",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"id": "evt_2Wc81Lk0",
"type": "call.bridged",
"created_at": "2026-11-03T15:42:07.412Z",
"livemode": true,
"account_id": "acc_ridgeline",
"data": {
"call_id": "cal_8Jq2mT4x",
"dial_session_id": "dls_31Ka9",
"agent_id": "agt_114",
"speech_ended_to_bridge_ms": 980,
"legs_canceled": 2
}
}