Fax Webhooks — Get Notified the Moment a Fax Finishes
Polling for a fax result wastes calls and adds lag. Instead, register a webhook endpoint and Send FAX Mail will POST a signed JSON event to your server the instant a fax is delivered, fails, or arrives on one of your numbers. You supply an HTTPS URL, pick the events you care about, and store the signing secret we show you. Every delivery carries an HMAC-SHA256 signature in an X-SFM-Signature header so your handler can prove the request came from us and not a forged caller. Webhooks are part of the Business and Enterprise plans and are managed alongside your API keys at /dashboard/developers.
Plan requirement
Outbound webhooks ship with the Business ($79.99/mo) and Enterprise ($169.99/mo) plans, the same tier as the REST API. The endpoint form is hidden on lower plans and rejected server-side, while delete stays available so a downgraded account can remove old endpoints.
How it works
When a fax changes state, the Telnyx event reaches us first; we then look at which of your endpoints subscribed to that event type and queue a delivery for each one. The delivery worker signs the JSON body with your endpoint's secret using HMAC-SHA256 and sends it in an X-SFM-Signature header shaped t=<timestamp>,v1=<signature>. The signed string is the header's timestamp, a literal dot, then the raw body (t.body) — your handler recomputes HMAC-SHA256 over that exact string and compares to v1; a match proves authenticity and the timestamp lets you reject stale replays. Delivery is at-least-once: a non-2xx response is retried with exponential backoff across several attempts before the event is parked in a dead-letter, so a brief outage on your side does not lose the notification.
What you can do
- ✓Subscribe to fax.delivered, fax.failed, and fax.received events per endpoint
- ✓Receive a signed JSON POST the moment a fax reaches a final state
- ✓Verify authenticity with the HMAC-SHA256 X-SFM-Signature header and your secret
- ✓Reject replays using the timestamp carried inside the signature header
- ✓Rely on at-least-once delivery with exponential-backoff retries and a dead-letter
- ✓Register multiple endpoints, each with its own event set and signing secret
Setup steps
- 1Confirm Business or Enterprise — the webhook endpoint form is hidden on lower plans
- 2Stand up an HTTPS URL that accepts POST; it must be publicly reachable and not an internal address
- 3At /dashboard/developers, add the endpoint, choose the events, and save the signing secret shown once
- 4In your handler, read the raw body and the X-SFM-Signature header before parsing JSON
- 5Recompute HMAC-SHA256 over the signed string — the header's t value, a literal dot, then the exact raw body (t.body) — with your secret and compare to the v1 value
- 6Return a 2xx quickly; do the real work asynchronously so retries are not triggered by a slow handler
Example
POST https://your-app.example.com/hooks/fax
X-SFM-Signature: t=1893456000,v1=9f86d0818...hmac...
Content-Type: application/json
{
"event": "fax.delivered",
"fax": {
"id": "65b1...",
"to": "+15551234567",
"status": "delivered",
"pages": 3
}
}Fax Webhooks — FAQ
Three: fax.delivered when an outbound fax lands, fax.failed when it cannot be delivered, and fax.received when a fax arrives on one of your numbers. You choose the set per endpoint when you register it, so a server that only cares about failures need not receive the rest.
Each POST carries an X-SFM-Signature header shaped t=<timestamp>,v1=<signature>. Recompute an HMAC-SHA256 over the signed string — the header's t value, a literal dot, then the exact raw request body (t.body) — using the endpoint's signing secret, then compare it to the v1 value. Reading the raw bytes before any JSON parsing matters, because re-serializing the object would change the signature. The timestamp also lets you discard anything too old to be a fresh event.
The delivery is retried. Anything other than a 2xx response is re-attempted with exponential backoff across several tries, and only after those are exhausted does the event move to a dead-letter. Because delivery is at-least-once, write your handler to tolerate the same event arriving more than once by keying on the fax id.
No. The URL must be HTTPS and publicly reachable; loopback, private-range, link-local, and cloud-metadata hosts are rejected when you save the endpoint. That guard exists so the delivery worker can never be steered at an internal service. Use a public tunnel during development if you need to receive events on a local machine.
For status they usually do — a fax.delivered or fax.failed event arrives without you asking, which is faster and lighter than a polling loop. You can still call GET /v1/faxes/:id at any time as a fallback or to reconcile, but most integrations let the webhook drive the state change and treat the read endpoint as a backup.
Build with the Fax Webhooks
API access and webhooks are included on the Business and Enterprise plans. Create a key at /dashboard/developers and start sending.
7-day free trial · No credit card required