Webhooks
Webhooks notify your server when relevant ZTL resources change. They help keep your system and customer-facing statuses up to date without waiting for the next poll. This includes changes that occur after a resource was previously considered final.
The currently available event family is PAYMENT_STATUS. Webhooks complement your
normal reconciliation process; retain resource IDs and use the relevant status
API when you need to reconcile an unknown or missed outcome.
Set up a subscription
Section titled “Set up a subscription”- Create a subscription with a public HTTPS endpoint and the event types you want to receive.
- Store the returned signing secret in your receiving service. It is shown only when the subscription is created or when you rotate it.
- Optionally configure a bearer token if your endpoint requires it.
- Call the test-delivery endpoint and confirm that your endpoint accepts the request.
Use the webhook API reference to create, list, update, and disable subscriptions. Subscriptions are available only to the authenticated partner that created them. Payment events are delivered only to subscriptions belonging to the partner that owns the payment.
Your endpoint must use HTTPS and be reachable from the public internet. Do not use a browser-return URL as a webhook endpoint; browser returns and webhooks serve different purposes. See callbacks for browser returns.
Authenticate subscription-management requests with your normal ZTL API access
token in Authorization: Bearer {accessToken}. This is separate from the optional
bearer token that ZTL sends to your receiving endpoint.
For example, send this JSON body to POST /api/webhooks/subscription:
{ "targetUrl": "https://partner.example/webhooks/ztl", "eventTypes": ["PAYMENT_STATUS"]}The response is HTTP 201 and includes the subscription id and signingSecret.
New subscriptions are enabled immediately. Creating a subscription does not
replay earlier payment events.
You can create multiple subscriptions, each with its own endpoint, event types,
and credentials. To update one, send both targetUrl and the complete desired
eventTypes list to PATCH /api/webhooks/subscription/{id}. At least one event
type is required. Listing subscriptions includes both enabled and disabled ones.
Choose event types
Section titled “Choose event types”Subscribe to the parent type PAYMENT_STATUS to receive all public payment status
changes. Subscribe to one or more child types when you only need selected
statuses. Event type names are case-sensitive.
| Subscription type | Receives transitions to |
|---|---|
PAYMENT_STATUS |
Any of the statuses below. |
PAYMENT_STATUS_UNSIGNED |
Unsigned |
PAYMENT_STATUS_IN_PROGRESS |
InProgress |
PAYMENT_STATUS_REJECTED |
Rejected |
PAYMENT_STATUS_COMPLETED |
Completed |
PAYMENT_STATUS_CANCELLED |
Cancelled |
PAYMENT_STATUS_CUSTOMER_ACTION_REQUIRED |
CustomerActionRequired |
Deliveries always identify the specific child type that occurred, including when
you subscribed through the parent type. For example, a subscription to
PAYMENT_STATUS receives an event with type
PAYMENT_STATUS_COMPLETED when a payment completes.
Selecting both the parent and a matching child does not create two deliveries for the same subscription. Separate matching subscriptions each receive a delivery.
Receive and verify deliveries
Section titled “Receive and verify deliveries”ZTL sends an HTTP POST request with a JSON body. A successful response is any
2xx response. Verify and persist the event before returning a successful
response, then process it asynchronously if your workflow needs more time.
Respond within 10 seconds. Your endpoint must present a valid, publicly trusted TLS certificate for its hostname; self-signed certificates are not supported. Creating a subscription validates the URL format, but does not verify endpoint reachability. Use the test endpoint to check delivery.
| Header | Purpose |
|---|---|
X-ZTL-Webhook-Id |
Identifies this delivery. Include it in support logs. |
X-ZTL-Webhook-Event |
The specific event type, such as PAYMENT_STATUS_COMPLETED. |
X-ZTL-Webhook-Timestamp |
Unix timestamp in seconds used for signature verification. |
X-ZTL-Webhook-Signature |
Signature in the form v1={hex-hmac}. |
Authorization |
Present only when you configured a bearer token. Its value is Bearer {token}. |
The signing secret is always used to sign deliveries. Verify the signature before parsing or acting on the body:
- Read the raw request body and the timestamp header.
- Reject timestamps outside the acceptance window your service defines.
- Compute an HMAC-SHA256 using the UTF-8 bytes of the signing secret as returned
by the API (do not Base64-decode it) over
{timestamp}.{raw request body}. - Compare the resulting hex value with the value after the
v1=prefix using a constant-time comparison.
Never expose, log, or send the signing secret to a browser. Treat a bearer token as an additional endpoint-authentication control, not as a replacement for signature verification.
Process events safely
Section titled “Process events safely”ZTL may retry a delivery that does not receive a successful response. Your handler must therefore tolerate duplicate deliveries and events that arrive out of order.
- Use the event body’s
idas your idempotency key. - Persist the received event before triggering any side effect in your system.
- Accept future event types and status values without failing deserialization.
- Use
occurredAtto detect older events and avoid overwriting a newer status with a delayed delivery. Reconcile ambiguous ordering through the payment status API. Do not assume that a previous final status cannot later change. - Do not display optional
statusReasonTextdirectly to an end user. It is intended for operational context and support.
ZTL retries network failures and HTTP 408, 429, and 5xx responses, with up
to six delivery attempts in total. Retry delays are approximately 1, 5, 30, 120,
and 360 minutes after successive failures. Other unsuccessful HTTP responses
are not retried. Redirects are not followed, and destinations that resolve to
non-public addresses are rejected without retry.
After retries are exhausted, automatic delivery stops. Reconcile any missed updates through the payment status API.
Investigate endpoint availability, TLS configuration, authentication, and signature verification before requesting support. Include the delivery ID and event ID in your support request, but never include secrets or bearer tokens.
Payment status events
Section titled “Payment status events”A PAYMENT_STATUS_* event is emitted when a payment’s public status changes.
The event type is always the specific status child type. paymentId identifies
the payment in the Payments API. previousStatus and status use the public
payment status values shown above, including their casing. A change to a status
reason alone does not produce an event.
statusReason is an optional code such as PartlySigned,
SmsConfirmationRequired, or InsufficientFunds. statusReasonText provides
optional additional context. Handle absent or null reason fields, and allow for
new status and reason values over time.
{ "id": "43ccf9e9-5026-4fb5-9614-a9cd44298912", "type": "PAYMENT_STATUS_CUSTOMER_ACTION_REQUIRED", "version": "1", "occurredAt": "2026-09-02T10:00:00Z", "data": { "paymentId": "6b0ce7dc-19ed-4e0c-87a2-3df0eeecfb4d", "previousStatus": "InProgress", "status": "CustomerActionRequired", "statusReason": "SmsConfirmationRequired", "statusReasonText": null }}The event ID identifies the status-change event. The delivery ID is separate and identifies delivery to a particular subscription. Retries reuse the delivery ID and event body, with a fresh timestamp and signature for each attempt.
Test an endpoint
Section titled “Test an endpoint”Call POST /api/webhooks/subscription/{id}/test for an enabled subscription.
It sends a webhook.test event regardless of the subscription’s selected event
types. The test payload contains id, type, and createdAt; it does not have
the payment event’s data object.
The API returns HTTP 200 with deliveryId, succeeded, and an optional error.
Check succeeded: HTTP 200 alone does not mean your endpoint received the
webhook successfully. On failure, error describes the delivery problem.
Retryable test failures follow the same retry policy as other deliveries. An
unknown, disabled, or other partner’s subscription returns HTTP 404.
Rotate credentials
Section titled “Rotate credentials”Rotate the signing secret with the signing-secret endpoint, and set or rotate a
bearer token with the bearer-token endpoint. Update the receiving service at the
same time as a rotation so it continues to accept deliveries. The API never
returns a configured bearer token; it returns only whether one is configured.
Send only the token value in bearerToken, without the Bearer prefix. Tokens
must not be blank. Updating a subscription does not change its bearer token;
use POST /api/webhooks/subscription/{id}/rotate-bearer-token for that.
Already queued deliveries retain the endpoint and credentials in effect when
they were queued. Allow an overlap period for old and new credentials while
those deliveries finish. Disabling a subscription stops new events from being
queued but does not cancel existing deliveries.
There is currently no endpoint to re-enable a disabled subscription or remove a configured bearer token. Create a replacement subscription if needed, and disable the old one when the replacement is ready.
Use the test-delivery endpoint after changing your endpoint, signing secret, or bearer token.