Webhooks API reference

Updated

8 operations of the Fax for Agents API, from the OpenAPI document version 1.20.0, snapshot of 2026-11-13. The base URL is https://api.faxforagents.com/v1. This is one group of the full reference.

  • The secret of a webhook is shown once, in the answer that created or rotated it.
  • After a secret rotation the old secret signs too for 24 hours.
  • A replay sends the event again with the same event id.

Operations

What is a webhook destination?

A URL of yours, an event filter and a secret. POST /v1/webhooks registers one. The body needs the url and can carry a description and event_types. An empty or missing list means every event type.

The answer carries the secret once. Every delivery to this URL is signed with it. The webhooks guide has the three headers and code that verifies the signature in Python and TypeScript.

How do I change or pause a destination?

PATCH /v1/webhooks/{webhook_id} changes the URL, the description and the filter. It also takes status, which pauses a destination and resumes it. A paused destination gets no deliveries. DELETE /v1/webhooks/{webhook_id} removes it. GET /v1/webhooks lists the destinations of the environment, without their secrets.

How is a secret rotated?

POST /v1/webhooks/{webhook_id}/rotate-secret returns a new secret. The summary states the overlap: the old one signs too for 24 hours. During that time each delivery carries two signatures, and a receiver that accepts either keeps working. previous_secret_expires_at on the destination says when the overlap ends.

So a rotation needs no window of downtime. Rotate, deploy the new secret to the receiver, and the old one stops signing by itself.

How do I test a receiver?

POST /v1/webhooks/{webhook_id}/test sends a signed webhook.test event to the destination and answers 202 with the event it sent. The receiver sees a real delivery with a real signature, and no fax is involved. Use it right after registering, and again after each rotation.

Where do I see what was delivered?

GET /v1/webhook-deliveries lists the delivery attempts. Each row has the event id and type, the attempt number, the status the receiver answered and the duration. It also has an error when there was one, and when the next attempt is due. The summary adds "no payload secrets". The list filters by webhook_id and by event_id, and pages with cursor and limit.

A receiver that answers outside the 2xx range gets the event again on a fixed schedule. The guide has the schedule with its date.

What does a replay do?

POST /v1/webhook-deliveries/{delivery_id}/replay sends the event of that delivery again and answers 202. The event id is the same as the first time. A handler that deduplicates on the id treats a replay of something it already handled as a no-op, and handles one it missed.

For a longer gap, the log in the events group returns every event in order, delivered or not.

Which scope do webhook calls need?

One: webhook:admin, for every operation in the group, the lists included. The default API key does not carry it. A call with it answers 403 scope_missing. Choose the scope when you create the key that registers webhooks.

An agent key is never issued webhook:admin. Where events go is decided by a person or by a key of the organization, not by the agent whose faxes they describe.

Do test and live events mix?

No. A destination belongs to the environment of the credential that registered it. A test key's destination gets the events of sandbox faxes, and a live key's destination gets the events of real ones.

Each operation in detail

GET /v1/webhook-deliveries

Delivery attempts (no payload secrets)

Credential: an API key or a signed-in session. Scope: webhook:admin.

Parameters

GET /v1/webhook-deliveries parameters
NameInTypeRequiredNotes
webhook_idquerystring or nullno
event_idquerystring or nullno
cursorquerystring or nullno
limitqueryintegerno Default 50.

Responses

GET /v1/webhook-deliveries responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

GET /v1/webhook-deliveries response fields
FieldTypeRequiredDescription
itemsarray of object Deliveryyes
next_cursorstring or nullno

Example

curl https://api.faxforagents.com/v1/webhook-deliveries \
  -H "Authorization: Bearer ffa_test_..."

POST /v1/webhook-deliveries/{delivery_id}/replay

Replay the event with the same event id

Credential: an API key or a signed-in session. Scope: webhook:admin.

Parameters

POST /v1/webhook-deliveries/{delivery_id}/replay parameters
NameInTypeRequiredNotes
delivery_idpathstringyes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Responses

POST /v1/webhook-deliveries/{delivery_id}/replay responses
StatusMeaning
202Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

POST /v1/webhook-deliveries/{delivery_id}/replay response fields
FieldTypeRequiredDescription
attemptintegeryes
created_atstringyes
delivered_atstring or nullyes
duration_msinteger or nullyes
errorstring or nullyes
event_idstringyes
event_typestringyes
idstringyes
next_attempt_atstring or nullyes
replay_ofstring or nullyes
response_statusinteger or nullyes
schedule_sarray of integeryes
statusstringyes
webhook_idstringyes

Example

curl https://api.faxforagents.com/v1/webhook-deliveries/DELIVERY_ID/replay \
  -X POST \
  -H "Authorization: Bearer ffa_test_..."

GET /v1/webhooks

List webhooks

Credential: an API key or a signed-in session. Scope: webhook:admin.

Responses

GET /v1/webhooks responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

GET /v1/webhooks response fields
FieldTypeRequiredDescription
itemsarray of object Webhookyes

Example

curl https://api.faxforagents.com/v1/webhooks \
  -H "Authorization: Bearer ffa_test_..."

POST /v1/webhooks

Register a destination (secret shown once)

Credential: an API key or a signed-in session. Scope: webhook:admin.

Parameters

POST /v1/webhooks parameters
NameInTypeRequiredNotes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Request body application/json, required

POST /v1/webhooks request body
FieldTypeRequiredDescription
descriptionstring or nullno
event_typesarray of string or nullnoEmpty or omitted: every event type.
urlstringyes

Responses

POST /v1/webhooks responses
StatusMeaning
201Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

POST /v1/webhooks response fields
FieldTypeRequiredDescription
created_atstringyes
descriptionstring or nullyes
event_typesarray of stringyes
idstringyes
previous_secret_expires_atstring or nullyes
secretstringyeswhsec_... Shown once. Verify webhook-signature with it.
statusstringyes
updated_atstringyes
urlstringyes

Example

curl https://api.faxforagents.com/v1/webhooks \
  -X POST \
  -H "Authorization: Bearer ffa_test_..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/webhooks/fax"}'

DELETE /v1/webhooks/{webhook_id}

Remove a destination

Credential: an API key or a signed-in session. Scope: webhook:admin.

Parameters

DELETE /v1/webhooks/{webhook_id} parameters
NameInTypeRequiredNotes
webhook_idpathstringyes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Responses

DELETE /v1/webhooks/{webhook_id} responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

DELETE /v1/webhooks/{webhook_id} response fields
FieldTypeRequiredDescription
deletedbooleanno
idstringyes

Example

curl https://api.faxforagents.com/v1/webhooks/WEBHOOK_ID \
  -X DELETE \
  -H "Authorization: Bearer ffa_test_..."

PATCH /v1/webhooks/{webhook_id}

Change url, filters or pause

Credential: an API key or a signed-in session. Scope: webhook:admin.

Parameters

PATCH /v1/webhooks/{webhook_id} parameters
NameInTypeRequiredNotes
webhook_idpathstringyes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Request body application/json, required

PATCH /v1/webhooks/{webhook_id} request body
FieldTypeRequiredDescription
descriptionstring or nullno
event_typesarray of string or nullno
statusstring (active, paused) or nullno
urlstring or nullno

Responses

PATCH /v1/webhooks/{webhook_id} responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

PATCH /v1/webhooks/{webhook_id} response fields
FieldTypeRequiredDescription
created_atstringyes
descriptionstring or nullyes
event_typesarray of stringyes
idstringyes
previous_secret_expires_atstring or nullyes
statusstringyes
updated_atstringyes
urlstringyes

Example

curl https://api.faxforagents.com/v1/webhooks/WEBHOOK_ID \
  -X PATCH \
  -H "Authorization: Bearer ffa_test_..." \
  -H "Content-Type: application/json" \
  -d '{}'

POST /v1/webhooks/{webhook_id}/rotate-secret

New secret; the old one signs too for 24 hours

Credential: an API key or a signed-in session. Scope: webhook:admin.

Parameters

POST /v1/webhooks/{webhook_id}/rotate-secret parameters
NameInTypeRequiredNotes
webhook_idpathstringyes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Responses

POST /v1/webhooks/{webhook_id}/rotate-secret responses
StatusMeaning
200Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

POST /v1/webhooks/{webhook_id}/rotate-secret response fields
FieldTypeRequiredDescription
created_atstringyes
descriptionstring or nullyes
event_typesarray of stringyes
idstringyes
previous_secret_expires_atstring or nullyes
secretstringyeswhsec_... Shown once. Verify webhook-signature with it.
statusstringyes
updated_atstringyes
urlstringyes

Example

curl https://api.faxforagents.com/v1/webhooks/WEBHOOK_ID/rotate-secret \
  -X POST \
  -H "Authorization: Bearer ffa_test_..."

POST /v1/webhooks/{webhook_id}/test

Send a signed webhook.test event to this destination

Credential: an API key or a signed-in session. Scope: webhook:admin.

Parameters

POST /v1/webhooks/{webhook_id}/test parameters
NameInTypeRequiredNotes
webhook_idpathstringyes
Idempotency-KeyheaderstringnoReplay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay.

Responses

POST /v1/webhooks/{webhook_id}/test responses
StatusMeaning
202Successful Response
401No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused.
403The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required.
404not_found: no such resource for this credential. A resource of another organization answers the same.
409The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state.
422A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule.
429rate_limited. Retry-After and the RateLimit-* headers say when to try again.

Response fields

POST /v1/webhooks/{webhook_id}/test response fields
FieldTypeRequiredDescription
correlation_idstring or nullyes
created_atstringyes
dataobjectyes
environmentstring (live, test)yes
fax_job_idstring or nullyes
idstringyes
org_idstringyes
project_idstring or nullyes
resource_idstringyes
resource_typestringyes
schema_versionintegeryes
typestringyes

Example

curl https://api.faxforagents.com/v1/webhooks/WEBHOOK_ID/test \
  -X POST \
  -H "Authorization: Bearer ffa_test_..."