Webhooks¶
AstraPBX delivers HTTP callbacks to a customer's app when call, queue, or CRM events occur. This page documents the event model, the delivery mechanism, and the test endpoint.
Model¶
A webhook is a row in the webhooks table (model api/src/models/Webhook.js) scoped to an org:
| Field | Meaning |
|---|---|
url | where to POST |
events | array of subscribed event types (validated against the allowed list) |
secret | optional HMAC signing secret |
active, retry_count, timeout | delivery controls |
last_delivery, last_status, failure_count | delivery health |
Event types¶
call.initiated call.ringing call.answered call.ended call.failed
queue.entered queue.abandoned queue.answered
user.registered user.unregistered trunk.registered trunk.failed
crm.lead.created crm.lead.updated crm.lead.status_changed
crm.client.created crm.client.updated
IVR DTMF events (
ivr.*) are not yet implemented — a requested feature (webhook on caller DTMF in an IVR).
Endpoints¶
All under /api/v1/webhooks, org-authenticated:
| Method | Path | Use |
|---|---|---|
| GET | /webhooks | list |
| POST | /webhooks | create (url + events[] required) |
| GET / PUT / DELETE | /webhooks/{id} | get / update / delete |
| POST | /webhooks/{id}/test | fire a single test delivery and report the result |
The POST /webhooks/{id}/test route exposes webhookService.testWebhook() so an integrator can verify their receiver is reachable and accepts the payload + signature.
Delivery¶
Delivery goes through api/src/services/webhookService.js:
deliverWebhook(orgId, eventType, payload)fetches all active webhooks for the org and filters in JS withwebhook.matchesEvent(eventType)(events.includes(eventType)).scheduleWebhookDelivery()POSTs the payload, retries on failure with progressive backoff ([1s, 5s, 15s, 60s, 300s]), and records success/failure on the row.
Payload + signature¶
{
"id": "<webhookId>_<ts>_<attempt>",
"event": "call.answered",
"timestamp": "2026-06-07T…Z",
"organization_id": "…",
"data": { … },
"attempt": 1
}
Headers include X-PBX-Event, X-PBX-Organization, X-PBX-Timestamp, X-PBX-Attempt, and — when a secret is set — X-PBX-Signature: sha256=<hmac> (HMAC-SHA256 of the JSON body). Verify it to confirm the delivery is genuinely from AstraPBX.
Where events fire¶
- Call / queue / user / trunk events:
api/src/services/eventListenerService.js(AMI + ARI listeners) →webhookService.onCall*/onQueue*. - CRM events:
api/src/routes/crm.jsfireWebhook(...)→webhookService.deliverWebhook. - Test / simulated call lifecycle:
POST /api/v1/test/call-event→triggerWebhooks()inserver.js.
The MariaDB delivery bug (fixed)¶
triggerWebhooks() in server.js used to select matching webhooks with Sequelize Op.contains, which is PostgreSQL-only. On this MariaDB deployment it matched nothing, so every webhook fired through that path (call.* / queue.* events and the test/call-event endpoint) was silently dropped. CRM webhooks were unaffected because they use webhookService.deliverWebhook (JS-side matching).
Fix: triggerWebhooks() now delegates to webhookService.deliverWebhook — JS-side event matching that works on MariaDB, plus HMAC signing, retries, and delivery-status recording. See troubleshooting Error 66.
Verified live on staging: crm.client.created, test, call.initiated, and call.answered all delivered with X-PBX-Signature; the broken path delivered nothing before the fix.
Related¶
- Interactive API Docs — the API reference where webhooks are documented and testable