Definition
A webhook delivers a POST request to your server when a fax event happens. Use it instead of polling fax or outbox endpoints. The current API isPOST /v3/webhooks. The older POST /v3/hooks API still works and is documented as legacy.
Webhooks must be enabled for your account. Open Settings → Integrations and enable webhooks before you call
/v3/webhooks. Until then the API returns 404 (application not found, enable webhooks in Fax.Plus integrations settings). The account also needs the webhooks feature; otherwise the API returns 403.Events
Use underscore names only. Dotted aliases are not accepted.
One endpoint can subscribe to several events via
filter_types.
Setting up webhooks
- Enable webhooks in Integrations. After you create an endpoint, copy its signing secret from that panel (see Verify signatures).
- Expose an HTTPS URL that accepts POST and returns 200 quickly.
- Create the endpoint with the API:
url and filter_types are required. filter_types must contain at least one event. A create that only sends url is 400.
Create returns 201 with an empty body. List endpoints afterward to read the ep_… id.
See Create a webhook endpoint for the full request.
Number filters (channels)
Optional channels is a list of E.164 numbers (+12135550123). The endpoint fires only when the fax from or to matches one of them.
You may include numbers you do not own — including the remote party. Values with a leading + are normalized to E.164 (+1 571 690 8695 → +15716908695). A number without + is rejected with 400 (Invalid number) — the request never creates or updates the endpoint.
- Omit
channels(or sendnullon create) to receive every matching event. - On
PATCH /v3/webhooks/{endpoint_id}, send"channels": nullto clear filters. Omitted fields stay unchanged.
Handling webhook notifications
- Method: HTTP POST, JSON body.
- Shape:
{ "hook": { … }, "data": { … } }— same shape as the legacy API. hook.target: always""on/v3/webhooksdeliveries. One event can go to several endpoints, so the payload cannot carry a single destination URL. Use your own endpoint URL, not this field. Legacy/v3/hooksstill fillstargetwith the configured URL.
Verify signatures
/v3/webhooks deliveries are signed with HMAC-SHA256. This is a shared signing secret, not a TLS or X.509 certificate. The REST API never returns the secret.
Where to get the secret
- Open Settings → Integrations.
- In the Webhooks section, click Manage. A panel opens.
- Create the webhook if it does not exist yet, then click it.
- Open Advanced and scroll to the bottom.
- Expand Signing Secret and use the eye icon to reveal it. Each endpoint has its own secret; rotating it invalidates the previous value.

Headers
Every current-API POST includes:How to verify
- Read the raw request body as bytes. Do not parse and re-serialize JSON first — whitespace changes break the signature.
-
Drop the
whsec_prefix from the secret and base64-decode the rest. That is the HMAC key. -
Build the signed string exactly as:
{webhook-id}.{webhook-timestamp}.{raw body}Example:msg_abc.1710000000.{"hook":…} -
Compute
HMAC-SHA256(key, signed_string)and base64-encode the digest. -
Compare that value to each
v1,signature inwebhook-signatureusing a constant-time compare. Accept if any matches. -
Reject the request if
webhook-timestampis more than 5 minutes off your clock (replay protection).
- Python
- JavaScript
Optional: allowlist source IPs
To allowlist source IPs, see the current list in the Fax.Plus webhooks help article.Payload
fax_sent
Fired when an outbound fax session finishes. Checkdata.status — it is not success-only.
fax_sent payload
If
hook.id is not equal to data.id, the fax is being retried. If they match, this is the first attempt.Payload schema
fax_received
Fired when an inbound fax session finishes.fax_received payload
Payload schema
Same schema asfax_sent.
fax_page_received
The
fax_page_received event is only available if your account has fax streaming enabled. This feature is available on request — contact us to discuss enabling it for your account.fax_page_received payload
Payload schema
Handling the notification
- Verify the HMAC.
- Parse
hook.eventanddata. - Apply your business logic. See Idempotency.
- Return 200 quickly. Long work belongs in a queue. Non-2xx responses are retried.
Idempotency
The same delivery can hit your URL more than once: automatic retries after a non-2xx, or a manual replay from the Integrations panel. Thewebhook-id HTTP header identifies that delivery. It is not a field in the JSON body. Retries and replays keep the same header value. A new fax event gets a new one.
hook.id and data.id in the payload identify the fax, not the delivery. Do not use them as the only dedup key.
Read webhook-id from the request headers and store each value you have already processed (cache or database, keep it for at least a few days). If it is already stored, skip the work and still return 200 so retries stop. If you return an error after you already applied the update, the next retry will send the same webhook-id header again.
API reference
Scopes:
fax:webhook:read / fax:webhook:edit, or fax:all:read / fax:all:edit.
Legacy /hooks API
GET/POST /v3/hooks and DELETE /v3/hooks/{hook_id} remain available. Each hook has one event, a target URL, and optional numbers (owned numbers only).
See List user webhooks (Legacy).