Webhooks¶
This page shows provider‑specific webhook endpoints, sample payloads, and signature verification examples.
Outbound Status — Phaxio¶
- Endpoint:
POST /phaxio-callback?job_id=<faxbot_job_id>&attempt_id=<attempt_id>. - Signature:
X-Phaxio-Signature, lowercase hexadecimal HMAC-SHA1 using the separate accountPHAXIO_CALLBACK_TOKEN.
Faxbot captures the callback override, or the accepted public API URL plus /phaxio-callback, with the job's original provider profile. It preserves unrelated query bytes/order and adds the job and attempt locators before submitting the URL. Verification uses that exact public URL, including its query and trailing slash; it does not reconstruct a URL from request or proxy headers. Both locators, the captured provider account and the provider fax ID must match. Query IDs alone grant no authority to update a job.
The signed message starts with that URL. Append form field names and decoded values, sorted stably by field name without separators. Then append each file-part name and its lowercase SHA1 content digest, sorted stably by part name. Repeated names retain their wire order; do not sort their values or collapse them into a dictionary. Encode the complete message as UTF-8, compute HMAC-SHA1 with the account Callback Token, and compare the lowercase hexadecimal result. This follows Phaxio's callback verification contract and api/app/provider_signatures.py.
Example form payload (Phaxio):
fax[id]=123456&fax[status]=success&fax[num_pages]=2
Verification through Faxbot's pure helper (Python; supplied values come from the captured attempt and parsed form):
from api.app.provider_signatures import verify_phaxio_signature
assert verify_phaxio_signature(
captured_callback_token,
captured_callback_url_with_locators,
ordered_form_fields, # list of (name, decoded string) pairs
ordered_file_parts, # list of (part name, file bytes) pairs
signature_header,
)
PHAXIO_API_SECRET authenticates send/status API calls and cannot substitute for the Callback Token. Missing/invalid signatures, a missing token, mismatched locators or a captured PHAXIO_VERIFY_SIGNATURE=false reject outbound callback updates. Disabling verification never enables unsigned updates. Later configuration changes do not replace a job's captured token, URL or verification setting. Status polling continues with the captured original account when a provider fax ID is available; it does not resubmit the fax. Received-fax notifications use the same verifier; see Receiving faxes.
The generated outbound callback source reference shows this build's exact verifier, URL construction and captured-attempt checks, with source hashes in its provenance.
Inbound — received faxes¶
Receiving faxes describes the whole flow: statuses, Fetch again, test faxes and the evidence Faxbot keeps. In short, nothing is recorded until a notification is checked. The document always comes from the provider's API by fax ID, or from an image inside the data folder, never from an address in the notification.
Phaxio¶
- Endpoint:
POST /phaxio-inbound. The signed URL isPUBLIC_API_URLfollowed by/phaxio-inbound. - Signature:
X-Phaxio-Signature, the same lowercase hexadecimal HMAC-SHA1 withPHAXIO_CALLBACK_TOKENas outbound callbacks, over that URL, the sorted form fields and the SHA-1 digest of each file part. - With
PHAXIO_INBOUND_VERIFY_SIGNATURE=false, Faxbot looks the fax up withGET https://api.phaxio.com/v2.1/faxes/{id}in the configured account before recording it, and ignores it when the account did not receive it.
Phaxio sends a multipart form. The fax object arrives as a JSON fax field, and the PDF arrives in a file part when files are sent with callbacks. A shortened example:
fax={"id":98765,"direction":"received","num_pages":3,"status":"success","from_number":"+15551230000","to_number":"+15559870000","completed_at":"2026-10-03T08:00:00.000-06:00"}
direction=received
is_test=false
success=true
file=<the PDF>
When there is no file part, Faxbot downloads GET https://api.phaxio.com/v2.1/faxes/{id}/file.
Sinch Fax API v3¶
- Endpoint:
POST /sinch-inbound - Basic auth:
SINCH_INBOUND_BASIC_USERandSINCH_INBOUND_BASIC_PASS. Sinch Fax API v3 does not sign webhooks. Unless both Basic auth values are set, Faxbot confirms the fax with Sinch's API before recording it. - With neither set, Faxbot looks the fax up with
GET /v3/projects/{projectId}/faxes/{id}before recording it. It downloads the document from/file.
A shortened example of an incoming fax event:
{
"event": "INCOMING_FAX",
"eventTime": "2026-10-03T14:00:01Z",
"fax": {
"id": "01F3J0G1M4WQR6HGY6HCF6JA0K",
"direction": "INBOUND",
"from": "+15551230000",
"to": "+15559870000",
"numberOfPages": 2,
"status": "COMPLETED",
"completedTime": "2026-10-03T14:00:00Z"
},
"file": "<base64 PDF>",
"fileType": "PDF"
}
Faxbot uses an attached file only when Basic auth authenticated the event.
SIP/Asterisk (self-hosted)¶
- Endpoint:
POST /_internal/asterisk/inbound - Header:
X-Internal-Secret: <ASTERISK_INBOUND_SECRET> tiff_pathmust be inside the Faxbot data folder. Paths through a symbolic link are refused. A repeateduniqueidis the same fax.
Body (JSON):
{
"tiff_path": "/faxdata/inbound/1603261234.89.tiff",
"to_number": "+15559870000",
"from_number": "+15551230000",
"faxstatus": "SUCCESS",
"faxpages": 2,
"uniqueid": "1603261234.89"
}
Security Tips¶
- Use HTTPS for all public callbacks.
- Keep secrets out of logs; audit only metadata (job ids, event types).
- Rotate webhook secrets periodically and validate signatures strictly.