Skip to content

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 with webhook.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.js fireWebhook(...)webhookService.deliverWebhook.
  • Test / simulated call lifecycle: POST /api/v1/test/call-eventtriggerWebhooks() in server.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.