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
Response example
{
  "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
Response example
{
  "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
Response example
{
  "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
Response example
{
  "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
Response example
{
  "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
Response schema: WebhookControlsReadiness

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.

Response example
{
  "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
Response schema: WebhookSigningState

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.

Response example
{
  "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
Response schema: WebhookSigningOperationResponse

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.

Response example
{
  "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
Response schema: WebhookSigningOperationResponse

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.

Response example
{
  "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
Response schema: WebhookHistoryPage

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.

Response example
{
  "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
Response schema: WebhookReplayResponse

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.

Response example
{
  "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
Response schema: SyntheticWebhookTestResponse

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.

Response example
{
  "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.
Node verification
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 };
}
Python verification
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.