Skip to main content
Webhook

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 is POST /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

  1. Enable webhooks in Integrations. After you create an endpoint, copy its signing secret from that panel (see Verify signatures).
  2. Expose an HTTPS URL that accepts POST and returns 200 quickly.
  3. 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 send null on create) to receive every matching event.
  • On PATCH /v3/webhooks/{endpoint_id}, send "channels": null to 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/webhooks deliveries. 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/hooks still fills target with 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

  1. Open Settings → Integrations.
  2. In the Webhooks section, click Manage. A panel opens.
  3. Create the webhook if it does not exist yet, then click it.
  4. Open Advanced and scroll to the bottom.
  5. Expand Signing Secret and use the eye icon to reveal it. Each endpoint has its own secret; rotating it invalidates the previous value.
Webhook Advanced settings with Signing Secret at the bottom
Treat it like a password. Do not commit it or expose it in client-side code. The REST API never returns this secret.

Headers

Every current-API POST includes:

How to verify

  1. Read the raw request body as bytes. Do not parse and re-serialize JSON first — whitespace changes break the signature.
  2. Drop the whsec_ prefix from the secret and base64-decode the rest. That is the HMAC key.
  3. Build the signed string exactly as: {webhook-id}.{webhook-timestamp}.{raw body} Example: msg_abc.1710000000.{"hook":…}
  4. Compute HMAC-SHA256(key, signed_string) and base64-encode the digest.
  5. Compare that value to each v1, signature in webhook-signature using a constant-time compare. Accept if any matches.
  6. Reject the request if webhook-timestamp is more than 5 minutes off your clock (replay protection).

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. Check data.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 as fax_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.
Fired once per received page so you can process pages before the fax completes.
fax_page_received payload

Payload schema

Handling the notification

  1. Verify the HMAC.
  2. Parse hook.event and data.
  3. Apply your business logic. See Idempotency.
  4. 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. The webhook-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

Legacy webhooks (/v3/hooks) will be decommissioned soon. Use /v3/webhooks for new integrations.
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).