Delivery Webhooks
Manage Faximus webhook endpoints and verify signed fax delivery callbacks before processing them.
Reference
Webhooks
GET/v1/webhook-endpointsList webhook endpointsRead active or disabled webhook destinations and subscribed event types. Soft-deleted endpoints are hidden from customer readbacks.webhook:manage
Read active or disabled webhook destinations and subscribed event types. Soft-deleted endpoints are hidden from customer readbacks.
- Required scope
- webhook:manage
- Authentication
- Bearer API key or customer session; account roles and scopes also apply
- Base URL
- https://api.faximus.io
- OpenAPI operation
- listWebhookEndpoints
{
"event": "customer_webhook_endpoint_list",
"items": [
{
"endpointId": "55555555-5555-4555-8555-555555555555",
"url": "https://hooks.example.com/faximus",
"safeLabel": "Primary delivery hook",
"status": "active",
"eventTypes": ["fax.delivery.confirmed", "fax.delivery.failed"],
"createdAt": "2026-06-20T14:12:48.000Z",
"updatedAt": "2026-06-20T14:12:48.000Z",
"disabledAt": null,
"deletedAt": null
}
]
}POST/v1/webhook-endpointsCreate a webhook endpointSubscribe an HTTPS destination to delivery events with a signing secret returned once.webhook:manage
Subscribe an HTTPS destination to delivery events with a signing secret returned once.
- Required scope
- webhook:manage
- Authentication
- Bearer API key or customer session; account roles and scopes also apply
- Base URL
- https://api.faximus.io
- OpenAPI operation
- createWebhookEndpoint
{
"event": "customer_webhook_endpoint_created",
"endpoint": {
"endpointId": "55555555-5555-4555-8555-555555555555",
"url": "https://hooks.example.com/faximus",
"safeLabel": "Primary delivery hook",
"status": "active",
"eventTypes": ["fax.delivery.confirmed", "fax.delivery.failed"],
"signingSecret": "whsec_exampleReturnedOnce",
"createdAt": "2026-06-20T14:12:48.000Z",
"updatedAt": "2026-06-20T14:12:48.000Z",
"disabledAt": null,
"deletedAt": null
}
}GET/v1/webhook-endpoints/{endpointId}Get a webhook endpointRead one active or disabled endpoint owned by the authenticated customer account.webhook:manage
Read one active or disabled endpoint owned by the authenticated customer account.
- Required scope
- webhook:manage
- Authentication
- Bearer API key or customer session; account roles and scopes also apply
- Base URL
- https://api.faximus.io
- OpenAPI operation
- getWebhookEndpoint
{
"event": "customer_webhook_endpoint_readback",
"endpoint": {
"endpointId": "55555555-5555-4555-8555-555555555555",
"url": "https://hooks.example.com/faximus",
"safeLabel": "Primary delivery hook",
"status": "active",
"eventTypes": [
"fax.delivery.confirmed",
"fax.delivery.failed",
"fax.delivery.unconfirmed"
],
"createdAt": "2026-06-20T14:12:48.000Z",
"updatedAt": "2026-06-20T14:12:48.000Z",
"disabledAt": null,
"deletedAt": null
}
}PATCH/v1/webhook-endpoints/{endpointId}Update a webhook endpointChange the endpoint URL, label, active/disabled status, or subscribed delivery events.webhook:manage
Change the endpoint URL, label, active/disabled status, or subscribed delivery events.
- Required scope
- webhook:manage
- Authentication
- Bearer API key or customer session; account roles and scopes also apply
- Base URL
- https://api.faximus.io
- OpenAPI operation
- updateWebhookEndpoint
{
"event": "customer_webhook_endpoint_updated",
"endpoint": {
"endpointId": "55555555-5555-4555-8555-555555555555",
"url": "https://hooks.example.com/faximus",
"safeLabel": "Primary delivery hook",
"status": "active",
"eventTypes": [
"fax.delivery.confirmed",
"fax.delivery.failed",
"fax.delivery.unconfirmed"
],
"createdAt": "2026-06-20T14:12:48.000Z",
"updatedAt": "2026-06-20T14:12:48.000Z",
"disabledAt": null,
"deletedAt": null
}
}DELETE/v1/webhook-endpoints/{endpointId}Delete a webhook endpointSoft delete an endpoint. Future deliveries stop and customer list/readback APIs hide the endpoint while audit history remains available.webhook:manage
Soft delete an endpoint. Future deliveries stop and customer list/readback APIs hide the endpoint while audit history remains available.
- Required scope
- webhook:manage
- Authentication
- Bearer API key or customer session; account roles and scopes also apply
- Base URL
- https://api.faximus.io
- OpenAPI operation
- deleteWebhookEndpoint
{
"event": "customer_webhook_endpoint_deleted",
"endpoint": {
"endpointId": "55555555-5555-4555-8555-555555555555",
"url": "https://hooks.example.com/faximus",
"safeLabel": "Primary delivery hook",
"status": "deleted",
"eventTypes": ["fax.delivery.confirmed", "fax.delivery.failed"],
"createdAt": "2026-06-20T14:12:48.000Z",
"updatedAt": "2026-06-20T15:04:12.000Z",
"disabledAt": "2026-06-20T15:04:12.000Z",
"deletedAt": "2026-06-20T15:04:12.000Z"
}
}GET/v1/webhook-endpoints/{endpointId}/readinessConditional callback readinessRead authorized endpoint/account/mode/version eligibility without creating work.Customer session
Read authorized endpoint/account/mode/version eligibility without creating work.
- Required scope
- Customer session
- Authentication
- Customer owner/admin session; API keys cannot manage credentials
- Base URL
- https://api.faximus.io
- OpenAPI operation
- getWebhookDeveloperReadiness
The composer can display this test-mode example but cannot run it. Check the Developer tools in your portal for supported backends and modes before running it with the SDK or cURL.
{
"schemaVersion": "faximus.webhook-controls-readiness.v1",
"backendReady": true,
"accountId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"endpointId": "55555555-5555-4555-8555-555555555555",
"businessMode": "test",
"role": "owner",
"actions": [
"history",
"rotate",
"revoke",
"replay",
"test"
],
"currentSecretVersionId": "22222222-2222-4222-8222-222222222222"
}GET/v1/webhook-endpoints/{endpointId}/signing-secretConditional signing stateInspect current and bounded previous signing version metadata; plaintext is never returned.Customer session
Inspect current and bounded previous signing version metadata; plaintext is never returned.
- Required scope
- Customer session
- Authentication
- Customer owner/admin session; API keys cannot manage credentials
- Base URL
- https://api.faximus.io
- OpenAPI operation
- getWebhookSigningSecretState
The composer can display this test-mode example but cannot run it. Check the Developer tools in your portal for supported backends and modes before running it with the SDK or cURL.
{
"tenantId": "11111111-1111-4111-8111-111111111111",
"accountId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"endpointId": "55555555-5555-4555-8555-555555555555",
"businessMode": "test",
"contextCleared": true,
"currentVersionId": "22222222-2222-4222-8222-222222222222",
"previousVersionId": null,
"previousValidUntil": null,
"emergencyRevoked": false,
"urlRevision": 1
}POST/v1/webhook-endpoints/{endpointId}/signing-secret/rotateConditional signing rotationRotate using the exact current version and a retained operation UUID.Customer session
Rotate using the exact current version and a retained operation UUID.
- Required scope
- Customer session
- Authentication
- Customer owner/admin session; API keys cannot manage credentials
- Base URL
- https://api.faximus.io
- OpenAPI operation
- rotateWebhookSigningSecret
The composer can display this test-mode example but cannot run it. Check the Developer tools in your portal for supported backends and modes before running it with the SDK or cURL.
{
"tenantId": "11111111-1111-4111-8111-111111111111",
"accountId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"endpointId": "55555555-5555-4555-8555-555555555555",
"businessMode": "test",
"contextCleared": true,
"currentVersionId": "44444444-4444-4444-8444-444444444444",
"previousVersionId": "22222222-2222-4222-8222-222222222222",
"previousValidUntil": "2026-10-05T19:00:00Z",
"emergencyRevoked": false,
"urlRevision": 1,
"operationId": "33333333-3333-4333-8333-333333333333",
"versionId": "44444444-4444-4444-8444-444444444444",
"alreadyApplied": false,
"firstApplied": true,
"signingSecret": "whsec_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}POST/v1/webhook-endpoints/{endpointId}/signing-secret/revokeConditional emergency revocationRevoke signing versions with current-version CAS and stable operation identity.Customer session
Revoke signing versions with current-version CAS and stable operation identity.
- Required scope
- Customer session
- Authentication
- Customer owner/admin session; API keys cannot manage credentials
- Base URL
- https://api.faximus.io
- OpenAPI operation
- revokeWebhookSigningSecrets
The composer can display this test-mode example but cannot run it. Check the Developer tools in your portal for supported backends and modes before running it with the SDK or cURL.
{
"tenantId": "11111111-1111-4111-8111-111111111111",
"accountId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"endpointId": "55555555-5555-4555-8555-555555555555",
"businessMode": "test",
"contextCleared": true,
"currentVersionId": "22222222-2222-4222-8222-222222222222",
"previousVersionId": null,
"previousValidUntil": null,
"emergencyRevoked": true,
"urlRevision": 1,
"operationId": "33333333-3333-4333-8333-333333333333",
"versionId": null,
"alreadyApplied": false,
"firstApplied": true
}GET/v1/webhook-endpoints/{endpointId}/deliveriesConditional callback historyRead bounded receiver-delivery history; callback failure does not establish fax failure.Customer session
Read bounded receiver-delivery history; callback failure does not establish fax failure.
- Required scope
- Customer session
- Authentication
- Customer owner/admin session; API keys cannot manage credentials
- Base URL
- https://api.faximus.io
- OpenAPI operation
- listWebhookDeliveryHistory
The composer can display this test-mode example but cannot run it. Check the Developer tools in your portal for supported backends and modes before running it with the SDK or cURL.
{
"items": [],
"nextCursor": null
}POST/v1/webhook-endpoints/{endpointId}/deliveries/replayConditional callback replayReplay an eligible retained terminal callback; this never resends the fax.Customer session
Replay an eligible retained terminal callback; this never resends the fax.
- Required scope
- Customer session
- Authentication
- Customer owner/admin session; API keys cannot manage credentials
- Base URL
- https://api.faximus.io
- OpenAPI operation
- replayWebhookDelivery
The composer can display this test-mode example but cannot run it. Check the Developer tools in your portal for supported backends and modes before running it with the SDK or cURL.
{
"tenantId": "11111111-1111-4111-8111-111111111111",
"accountId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"endpointId": "55555555-5555-4555-8555-555555555555",
"businessMode": "test",
"contextCleared": true,
"value": {
"deliveryId": "77777777-7777-4777-8777-777777777777",
"eventId": "44444444-4444-4444-8444-444444444444",
"alreadyApplied": false
}
}POST/v1/webhook-endpoints/{endpointId}/testConditional synthetic callbackQueue a callback for an authorized completed test job; no real fax is created or sent.Customer session
Queue a callback for an authorized completed test job; no real fax is created or sent.
- Required scope
- Customer session
- Authentication
- Customer owner/admin session; API keys cannot manage credentials
- Base URL
- https://api.faximus.io
- OpenAPI operation
- sendTestWebhook
The composer can display this test-mode example but cannot run it. Check the Developer tools in your portal for supported backends and modes before running it with the SDK or cURL.
{
"tenantId": "11111111-1111-4111-8111-111111111111",
"accountId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"endpointId": "55555555-5555-4555-8555-555555555555",
"businessMode": "test",
"contextCleared": true,
"value": {
"jobId": "63b3ab55-23b2-4a57-bf1c-519f4b151ed6",
"operationId": "33333333-3333-4333-8333-333333333333",
"eventId": "44444444-4444-4444-8444-444444444444",
"deliveryId": "77777777-7777-4777-8777-777777777777",
"alreadyApplied": false
}
}Webhook Security
Verify delivery signatures
Verify the transmitted raw bytes with the endpoint signing secret and reject stale signatures before accepting or deduplicating a callback.
Headers sent with each delivery
- Faximus-Event-Type contains values such as fax.delivery.confirmed or fax.delivery.failed.
- Deduplicate eventId from the verified signed payload. If Faximus-Event-Id is present, require it to match that verified eventId.
- Faximus-Signature uses t=<unix timestamp>,v1=<hex hmac sha256>.
- Compute HMAC over the exact string `${timestamp}.${rawBody}` using the endpoint signing secret.
- Reject old timestamps, for example more than five minutes from your server clock.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(rawBody, signature, secrets, options = {}) {
if (!(rawBody instanceof Uint8Array) || typeof signature !== 'string' || !Array.isArray(secrets) || secrets.some(secret => typeof secret !== 'string')) return null;
const match = /^t=(0|[1-9][0-9]{0,11}),v1=([0-9a-f]{64})$/.exec(signature);
const now = options.nowSeconds ?? Math.floor(Date.now() / 1000), tolerance = options.toleranceSeconds ?? 300;
if (!match || !Number.isFinite(now) || !Number.isFinite(tolerance) || tolerance < 0 || tolerance > 3600 || Math.abs(now - Number(match[1])) > tolerance || !secrets.length || secrets.length > 2) return null;
const input = Buffer.concat([Buffer.from(`${match[1]}.`), Buffer.from(rawBody)]), expected = Buffer.from(match[2], 'hex');
let valid = false; for (const secret of secrets) { if (!secret) continue; valid = timingSafeEqual(createHmac('sha256', secret).update(input).digest(), expected) || valid; }
if (!valid) return null;
let payload; try { payload = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(rawBody)); } catch { return null; }
if (!payload || payload.schemaVersion !== 'faximus.webhook.v1' || typeof payload.eventId !== 'string' || !payload.eventId || /[\uD800-\uDFFF]/u.test(payload.eventId) || Buffer.byteLength(payload.eventId, 'utf8') > 128 || (options.eventIdHeader !== undefined && options.eventIdHeader !== payload.eventId)) return null;
return { eventId: payload.eventId, payload };
}
import hmac
import json
import math
import re
import time
from typing import Any
def verify_webhook(raw_body: bytes, signature: str, secrets: list[str], *, now_seconds: float | None = None,
tolerance_seconds: float = 300, event_id_header: str | None = None) -> dict[str, Any] | None:
if not isinstance(raw_body, bytes) or not isinstance(signature, str) or not isinstance(secrets, list) or any(not isinstance(secret, str) for secret in secrets):
return None
match = re.fullmatch(r"t=(0|[1-9][0-9]{0,11}),v1=([0-9a-f]{64})", signature)
now = time.time() if now_seconds is None else now_seconds
if not match or not math.isfinite(now) or not math.isfinite(tolerance_seconds) or not 0 <= tolerance_seconds <= 3600 or abs(now - int(match[1])) > tolerance_seconds or not 1 <= len(secrets) <= 2:
return None
expected = bytes.fromhex(match[2])
valid = False
for secret in secrets:
if secret:
valid = hmac.compare_digest(hmac.digest(secret.encode(), match[1].encode() + b"." + raw_body, "sha256"), expected) or valid
if not valid:
return None
try:
def reject_constant(value: str):
raise ValueError("Non-JSON constant")
payload = json.loads(raw_body.decode("utf-8"), parse_constant=reject_constant)
except (ValueError, UnicodeError):
return None
if not isinstance(payload, dict) or payload.get("schemaVersion") != "faximus.webhook.v1" or not isinstance(payload.get("eventId"), str):
return None
try:
event_id_bytes = payload["eventId"].encode("utf-8")
except UnicodeError:
return None
if not 1 <= len(event_id_bytes) <= 128 or (event_id_header is not None and event_id_header != payload["eventId"]):
return None
return {"eventId": payload["eventId"], "payload": payload}
Delivery behavior
- Return any 2xx status to acknowledge delivery.
- Non-2xx responses and network failures are retried with bounded backoff.
- Keep verification based on the raw request body, before JSON parsing mutates whitespace or ordering.
- The signing secret is shown only once in the create response and is not included in list or readback responses.