Webhooks API
Webhooks let the platform notify your other systems the moment something happens — a new contact, a reply, an appointment booked, and more. This API manages the subscriptions themselves: which URLs receive which events. For how to receive and verify the payloads your endpoint gets, see Webhooks.
All paths below are relative to the API base URL:
https://api.youraiconnector.com/v1
Every request must be authenticated. See Authentication for the four accepted methods. The examples here use the X-API-Key header (and one query-parameter form for cURL).
Note: Webhooks must be enabled for your account. If they are not, these endpoints return a 403.
How subscriptions are addressed
Each subscription has an id and an optional name. Either one can be used as the {webhookId} in the path for update, delete, test, health, and re-enable.
Prefer the name. Subscription ids are positional, so they can shift after another subscription is deleted. If you set a stable
namewhen creating a subscription, address it by name to avoid surprises.
List subscriptions
GET /webhooks
cURL
curl "https://api.youraiconnector.com/v1/webhooks?apiKey=YOUR_API_KEY"
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
Python
import requests
res = requests.get(
"https://api.youraiconnector.com/v1/webhooks",
headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
Response
{
"success": true,
"webhooks": [
{
"id": "0",
"name": "Order updates hook",
"url": "https://hooks.example.com/incoming",
"subscribed_to": ["Contact Created", "Replies"],
"subscribed_to_tags": [],
"created_at": "2026-06-09T12:00:00.000Z",
"signing_enabled": true,
"signing_secret_created_at": "2026-07-15T09:30:00.000Z",
"retries_enabled": true,
"enabled": true,
"apply_to_sub_accounts": false
}
]
}
signing_enabled and retries_enabled are per-subscription opt-ins, both off unless you turn them on. See Signed payloads and Retries.
apply_to_sub_accounts is the agency-inheritance opt-in — see One subscription for all client accounts. Off by default, and inert on accounts that have no client accounts.
enabled is the subscription’s on/off switch — see Switching a subscription off. Switched-off subscriptions are still listed here.
The signing secret itself is never included here — read it from GET /webhooks/{id}/signing-secret.
List subscribable event types
Returns the exact strings you may use in subscribed_to. Use this to discover valid event names rather than hard-coding them.
GET /webhooks/events
cURL
curl "https://api.youraiconnector.com/v1/webhooks/events" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/events", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
Python
import requests
res = requests.get(
"https://api.youraiconnector.com/v1/webhooks/events",
headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
Response
The response is {"success": true, "events": [...]}, where events currently holds 22 exact strings: Contact Created, Human Alerted, Appointment Booked, Replies, Reads, Deliveries, Credits Spent, Credits Recharged, Low Credit Balance, Contact Paused, Contact Do Not Disturb, Contact Unarchived, New Message, Contact Resumed, Chat Concluded, Task Created, Task Updated, Task Completed, Daily Summary Created, Channel Connected, Broadcast Started, and Broadcast Completed (Channel Connected is accepted in subscribed_to but nothing emits it today, so don’t build against it).
For what each event means and the event code it sends in the payload, see The 22 Webhook Events. This endpoint is the authoritative list at any moment — read it live rather than hard-coding the names.
Create a subscription
POST /webhooks
| Field | Required | Description |
|---|---|---|
url |
Yes | HTTPS URL that will receive event payloads via POST. Must be publicly reachable. |
subscribed_to |
Yes | A non-empty array of event names (see /webhooks/events). |
name |
No | A display name. Also usable as the {webhookId} later. Defaults to a timestamped name. |
subscribed_to_tags |
No | Tag IDs that narrow which tags produce a conversation-summary notification. It does not scope the subscription’s events to those tags — to get a request when a specific tag is applied, set a webhook URL on that tag in the Tags tab of the agent (or campaign). |
retries_enabled |
No | Boolean, defaults to false. Opt in to retries of failed deliveries. |
generate_signing_secret |
No | Boolean, defaults to false. Mint an HMAC signing secret with the subscription. The secret is returned once, as a top-level signing_secret on the response. |
enabled |
No | Boolean, defaults to true. Pass false to create the subscription switched off. See Switching a subscription off. |
apply_to_sub_accounts |
No | Boolean, defaults to false. On an agency account, true makes this subscription also receive events from every client account — see One subscription for all client accounts. |
URL rules: The URL must use
https://and be publicly reachable. Plainhttp://,localhost, private-network addresses, and platform-internal addresses are rejected with a400.
cURL
curl -X POST "https://api.youraiconnector.com/v1/webhooks?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/incoming",
"subscribed_to": ["Contact Created", "Replies"],
"name": "Order updates hook"
}'
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks", {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://hooks.example.com/incoming",
subscribed_to: ["Contact Created", "Replies"],
name: "Order updates hook",
}),
});
const data = await res.json();
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/webhooks",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"url": "https://hooks.example.com/incoming",
"subscribed_to": ["Contact Created", "Replies"],
"name": "Order updates hook",
},
)
data = res.json()
Response
{
"success": true,
"webhook_id": "1",
"webhook": {
"id": "1",
"name": "Order updates hook",
"url": "https://hooks.example.com/incoming",
"subscribed_to": ["Contact Created", "Replies"],
"subscribed_to_tags": [],
"created_at": "2026-06-09T12:00:00.000Z"
}
}
Update a subscription
Provide at least one of url, subscribed_to, name, subscribed_to_tags, retries_enabled, enabled, or apply_to_sub_accounts. Omitted fields keep their current values. subscribed_to and subscribed_to_tags are replacements, not merges.
PUT /webhooks/{webhookId}
Updating a subscription never disturbs its signing secret — manage that through the signing-secret routes.
When the URL changes, delivery for the new URL is automatically re-enabled, giving a previously failing endpoint a fresh start.
cURL
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/Order%20updates%20hook" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/v2/incoming",
"subscribed_to": ["Replies", "Chat Concluded"]
}'
JavaScript
const res = await fetch(
`https://api.youraiconnector.com/v1/webhooks/${encodeURIComponent("Order updates hook")}`,
{
method: "PUT",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://hooks.example.com/v2/incoming",
subscribed_to: ["Replies", "Chat Concluded"],
}),
}
);
const data = await res.json();
Python
import requests
res = requests.put(
"https://api.youraiconnector.com/v1/webhooks/Order updates hook",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"url": "https://hooks.example.com/v2/incoming",
"subscribed_to": ["Replies", "Chat Concluded"],
},
)
data = res.json()
Response
{
"success": true,
"webhook_id": "0",
"webhook": {
"id": "0",
"name": "Order updates hook",
"url": "https://hooks.example.com/v2/incoming",
"subscribed_to": ["Replies", "Chat Concluded"],
"subscribed_to_tags": [],
"created_at": "2026-06-09T12:00:00.000Z"
}
}
An unknown id or name returns 404 with { "success": false, "error": "Webhook not found" }.
Delete a subscription
Removes the subscription so its URL stops receiving payloads. Its delivery-health counters are reset, so re-adding the same URL later starts with a clean record.
DELETE /webhooks/{webhookId}
cURL
curl -X DELETE "https://api.youraiconnector.com/v1/webhooks/0" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/0", {
method: "DELETE",
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
Python
import requests
res = requests.delete(
"https://api.youraiconnector.com/v1/webhooks/0",
headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
Response
{
"success": true
}
Send a test payload
Sends a sample payload to the subscription’s URL so you can verify your receiver end-to-end. Optionally pass an event to control which event type the sample simulates. Test deliveries never affect the subscription’s health counters.
POST /webhooks/{webhookId}/test
The response always returns 200 and reports the outcome with a delivered flag — a failed test does not return an error status. When delivered is false, the response includes the failure details.
| Field | Required | Description |
|---|---|---|
event |
No | Event type to simulate (must be one of /webhooks/events). Defaults to a delivery event. |
cURL
curl -X POST "https://api.youraiconnector.com/v1/webhooks/0/test?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "event": "Contact Created" }'
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/0/test", {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ event: "Contact Created" }),
});
const data = await res.json();
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/webhooks/0/test",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"event": "Contact Created"},
)
data = res.json()
Response (delivered)
{
"success": true,
"webhook_id": "0",
"delivered": true
}
Response (failed)
{
"success": true,
"webhook_id": "0",
"delivered": false,
"failure_type": "permanent",
"status_code": 404,
"error_message": "Request failed with status code 404"
}
failure_type is one of permanent, temporary, timeout, network, or unknown.
Check delivery health
Returns the delivery-health record for the subscription’s URL: how many deliveries have succeeded and failed, whether delivery is currently paused after repeated failures, and the details of the most recent failure. Returns "health": null when no deliveries have been attempted yet.
GET /webhooks/{webhookId}/health
cURL
curl "https://api.youraiconnector.com/v1/webhooks/0/health" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/0/health", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
Python
import requests
res = requests.get(
"https://api.youraiconnector.com/v1/webhooks/0/health",
headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
Response
{
"success": true,
"webhook_id": "0",
"url": "https://hooks.example.com/incoming",
"health": {
"consecutive_failures": 0,
"total_failures": 2,
"total_successes": 120,
"is_disabled": false,
"disabled_at": null,
"disabled_reason": null,
"last_failure": null,
"last_success_at": "2026-06-09T12:00:00.000Z",
"created_at": "2026-05-01T08:00:00.000Z",
"updated_at": "2026-06-09T12:00:00.000Z"
}
}
When is_disabled is true, delivery to the URL has been paused automatically after repeated failures. Fix your receiver, then re-enable it (below).
Re-enable delivery
Resumes delivery for a webhook whose URL was paused automatically after repeated failures. This resets the paused flag and failure counters but does not attempt a delivery — use the test endpoint afterwards to confirm your receiver is healthy again.
POST /webhooks/{webhookId}/reenable
cURL
curl -X POST "https://api.youraiconnector.com/v1/webhooks/0/reenable?apiKey=YOUR_API_KEY"
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/0/reenable", {
method: "POST",
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/webhooks/0/reenable",
headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
Response
{
"success": true,
"webhook_id": "0"
}
Switching a subscription off
enabled is the subscription’s own on/off switch. Switching it off stops deliveries while keeping the URL, event list and signing secret intact.
# Off
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/0?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
# Back on
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/0?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'
- Absent means on. A subscription created before this field existed has no
enabledvalue stored and delivers normally.GET /webhooksalways reports a concrete boolean. - Switched-off subscriptions are still listed by
GET /webhooks— that’s how you find them to switch back on. - A retry queued before the switch-off does not resume: the retry re-reads the subscription at send time and drops if it is switched off.
- Nothing suppressed while switched off is replayed when you switch it back on.
Distinct from the automatic disable after repeated failures, which is reported by
GET /webhooks/{id}/healthasis_disabledand cleared withPOST /webhooks/{id}/reenable.enabledis the account’s switch;is_disabledis ours. Neither overrides the other — a subscription must be both switched on and not auto-disabled to deliver.
One subscription for all client accounts (agencies)
On an agency account, set apply_to_sub_accounts: true on a subscription (at create time or via PUT) and it also receives events that happen on every one of the agency’s client accounts — one endpoint covers the whole agency, instead of re-creating the subscription on each client account.
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/0?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"apply_to_sub_accounts": true}'
How it behaves:
- The
userblock tells the accounts apart. Every payload’suserblock identifies the account the event actually happened on, so your receiver can route per client. - The agency subscription’s own settings apply everywhere. Its event list, signing secret and retry opt-in are used for the inherited deliveries too.
- A client account’s own subscription to the same URL wins. If a client account has its own subscription pointing at the same URL, that one is used for that account’s events — the same event is never delivered twice to one endpoint.
- Client accounts don’t see it. Inherited subscriptions do not appear in a client account’s own webhook list, and the client can’t switch them off — only the agency manages them.
- Delivery health is tracked per client account. An endpoint that keeps failing is auto-disabled for the account whose deliveries failed, not for the whole agency.
subscribed_to_tagsdoes not inherit. The tag list references the agency’s own tags, which don’t exist on client accounts — conversation-summary narrowing only applies to the agency’s own events.- Inert elsewhere. On an account with no client accounts the flag stores fine and does nothing.
Headers on every delivery
These three headers are sent on every delivery, whether or not the subscription is signed:
| Header | Meaning |
|---|---|
X-Webhook-Delivery |
Stable id for the logical event. Identical across retries — dedupe on it. |
X-Webhook-Attempt |
1-based attempt number. |
X-Webhook-Event |
The event name. |
Signed payloads
Signing is optional, off by default, and set per subscription. When a subscription has a signing secret, every delivery carries two more headers on top of the three sent on every delivery (X-Webhook-Delivery, X-Webhook-Attempt and X-Webhook-Event):
| Header | Meaning |
|---|---|
X-Webhook-Signature |
v1=<hex> — HMAC-SHA256 of the string "<timestamp>.<raw request body>", keyed with the per-webhook signing secret you mint and rotate at GET/POST/DELETE /v1/webhooks/{webhookId}/signing-secret. |
X-Webhook-Timestamp |
Send time, Unix seconds. Bound into the signature, so it cannot be altered independently. |
To verify, recompute the HMAC-SHA256 over the raw body with your secret and compare it to the header. Verify against the raw request body. Re-serializing parsed JSON changes the bytes and breaks the comparison. Reject deliveries whose timestamp is outside a freshness window (300s is a sensible default) to prevent replay, and compare with a timing-safe function.
See Signed Payloads for full Node and Python verification examples.
Signing is not the same as API authentication. The REST API itself authenticates with API keys rather than OAuth (OAuth 2.1 does exist for MCP servers you register as bot tools), and there are no official npm or PyPI SDK packages yet — call the endpoints with any HTTP client.
Read the signing secret
GET /webhooks/{id}/signing-secret
curl "https://api.youraiconnector.com/v1/webhooks/0/signing-secret?apiKey=YOUR_API_KEY"
Response
{
"success": true,
"webhook_id": "0",
"signing_enabled": true,
"signing_secret": "whsec_1a2b3c...",
"signing_secret_created_at": "2026-07-15T09:30:00.000Z"
}
When signing is off, signing_enabled is false and signing_secret is null.
Generate or rotate the signing secret
POST /webhooks/{id}/signing-secret
Creates a secret (turning signing on) or replaces the existing one. Returns the new secret.
curl -X POST "https://api.youraiconnector.com/v1/webhooks/0/signing-secret?apiKey=YOUR_API_KEY"
Response
{
"success": true,
"webhook_id": "0",
"signing_enabled": true,
"signing_secret": "whsec_9f8e7d...",
"signing_secret_created_at": "2026-07-15T10:00:00.000Z"
}
Rotation takes effect immediately — the next delivery is signed with the new secret only. Accept both secrets briefly while you roll the change out to a live endpoint.
You can also mint a secret at creation time by passing "generate_signing_secret": true to POST /webhooks; the response then includes a top-level signing_secret field.
Turn signing off
DELETE /webhooks/{id}/signing-secret
curl -X DELETE "https://api.youraiconnector.com/v1/webhooks/0/signing-secret?apiKey=YOUR_API_KEY"
Response
{
"success": true,
"webhook_id": "0",
"signing_enabled": false
}
All three signing-secret routes require the Integrations edit permission, including the
GET— the secret is a credential that can forge deliveries, so it is not exposed to read-only roles.
Retries
Optional, off by default, and set per subscription via the retries_enabled boolean on POST /webhooks or PUT /webhooks/{id}.
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/0?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"retries_enabled": true}'
When enabled, a failed delivery is retried at 1m, 5m, 30m, and 2h after the first attempt (about 2h40m of coverage).
- Retried: 5xx responses, timeouts, and connection failures.
- Not retried: any 4xx. The receiver is rejecting the request itself, so replaying it unchanged only reproduces the rejection.
Retries make duplicate delivery possible — an endpoint that processed an event but timed out before responding will see it again. Dedupe on X-Webhook-Delivery, which is constant across attempts. This is why retries are opt-in.
The delivery-health counters count a whole delivery, not each attempt: a failure is recorded only once every retry is exhausted, so enabling retries does not make the automatic disable trigger sooner.
Errors
All errors use the standard envelope:
{
"success": false,
"error": "Webhook not found"
}
Common cases: a URL that is not allowed, an empty/invalid subscribed_to, or missing fields return 400; an unknown id or name returns 404; and a 403 means webhooks are not enabled for your account. See Errors for the full list.
Next steps
- Webhooks (receiving payloads) — set up your receiver and understand the payload shape.
- Authentication — the four ways to authenticate a request.
- Errors & Rate Limits — status codes and the 300 req/min limit.