Skip to main content

Outbound webhooks

A webhook endpoint is an address on your side that MonoPbx calls when something happens in your company: a call starts or ends, a queue changes, a device registers, a text message arrives. Every event that an endpoint's filter selects is sent to it as a signed JSON POST. Delivery is at least once, so your receiver must be able to see the same event twice.

Webhooks are the only automation channel in this release.

Set up an endpoint​

  1. Make sure the receiver is reachable at a public https address on port 443 (or another port from 1024 up). Loopback, private-network and internal names are refused.
  2. Create the endpoint with the API (POST /api/v1/webhooks, permission integrations:write). Choose the events to receive in event_types, or leave it empty for every event the endpoint is allowed to see.
  3. Copy the signing secret from the answer. It is shown once. If you lose it, rotate it.
  4. Press Test (POST /api/v1/webhooks/{id}/test). MonoPbx sends a signed webhook.test event at once and returns the delivery result.

A company can have up to 20 endpoints. Tests and redeliveries are limited to 10 per company each minute.

Choose the events​

event_types takes event types (call.ended) or topic patterns (call.*). Every event type of the events WebSocket can be selected, except audit records. The endpoint also receives:

  • cdr.written: a call record was saved. The record's fields are the ones in the call-records API. The recording reference, the cost and the internal participants are not included.
  • webhook.test: only from the test button. You cannot subscribe to it.

Who receives what​

An endpoint receives only the events its creator is allowed to read across the whole company. The permission for each kind of event:

EventsPermission
call.*, leg.*calls:control
queue.*queues:read
presence.*directory:read
voicemail.*voicemail:read
device.*devices:read
sms.*messaging:read
cdr.writtencall_history:read
config.changedthe read permission of the changed kind of entry, such as users:read or trunks:read
audit.*never delivered

If you create an endpoint without one of these permissions, the events that need it are not sent to you. An explicit filter entry you are not allowed to receive is refused, and the list of event types offers only the ones you may receive. Changing an endpoint's URL or filter resets its grant to the permissions of the person who made the change.

Verify the signature​

Every delivery carries these headers, following the Standard Webhooks specification:

HeaderMeaning
webhook-idThe event id. Same on every attempt and redelivery. Use it to ignore duplicates.
webhook-timestampUnix seconds of this attempt.
webhook-signaturev1,<base64>: an HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{body}. During a secret rotation there are two space-separated signatures.
content-typeapplication/json

Use any Standard Webhooks library and pass the raw request body and the headers. The library rejects a bad signature and a timestamp older than five minutes.

from standardwebhooks.webhooks import Webhook

Webhook("whsec_...").verify(request.body, request.headers) # raises if invalid or older than 5 min
import { Webhook } from "standardwebhooks";

new Webhook("whsec_...").verify(rawBody, headers);

Verify against the raw body bytes, before any JSON parsing or re-serialisation.

Rotate the secret​

POST /api/v1/webhooks/{id}/rotate-secret returns a new secret, shown once. The old secret keeps signing for a grace period (24 hours by default, at most 7 days, or 0 for none). Update your receiver during that period so that no delivery is refused.

Retries and dead letters​

A delivery is recorded before its first attempt, so a restart of MonoPbx does not lose it.

Answer from your receiverWhat happens
Any 2xxDelivered.
Any other status, a timeout, or a network or DNS failureRetried after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, then 10 hours twice. A Retry-After header of up to an hour lengthens the wait.
The eighth failureDead. It stays in the dead-letter view for 30 days and can be redelivered.
A 3xx answerA failure. Redirects are never followed.
A blocked address or a body over 256 KiBDead at once.

The response body from your receiver is never read or stored. To send a dead delivery again, use POST /api/v1/webhooks/deliveries/{id}/redeliver; it gets eight fresh attempts with the same webhook-id.

Each endpoint has a rate limit (600 deliveries a minute by default, up to 6000). Deliveries above the limit wait a few seconds.

Monitor deliveries​

  • GET /api/v1/webhooks shows each endpoint with its pending and dead counts.
  • GET /api/v1/webhooks/deliveries?filter=status:eq:dead is the dead-letter view.
  • GET /api/v1/webhooks/deliveries/{id}/attempts shows every attempt: time, status code, latency and outcome. It is kept for 90 days.

Limits​

  • Only https destinations are allowed, on port 443 or 1024 and above. Credentials in the address are refused.
  • Destinations on private, loopback, link-local (including cloud metadata addresses), carrier-grade NAT and reserved networks are refused, both when you save the endpoint and on every attempt.
  • The receiver must answer with 2xx to count as delivered.

Event envelope​

The body of every delivery has the same envelope: id, tenant_id, occurred_at, schema, type and data. The JSON Schemas are in the docs/api/events.schema.json file of the product repository.