Skip to main content
Webhooks let Cleo Pay push real-time notifications to your server when important events occur — such as a payable being approved, a payment completing or failing, or a bank account becoming verified. Instead of polling the API, you receive an HTTP POST request at your configured endpoint the moment an event fires.
Webhook endpoints (URL, event subscriptions, and signing secrets) are managed through the Cleo Pay dashboard. Your signing secret is shown once — when the endpoint is created or the secret is rotated — so store it securely.

How webhooks work

When a subscribed event occurs, Cleo Pay sends an HTTP POST request with a JSON body to your endpoint. Every delivery is signed — see Verifying deliveries. Your endpoint must respond with any 2xx status code within 10 seconds to acknowledge receipt; the response body is ignored.
If your endpoint does not return a 2xx response — due to a timeout, connection error, or non-2xx status — Cleo Pay retries the delivery with exponential backoff. The same event may be delivered more than once. Process events idempotently by deduplicating on the event id.

Payload structure

Every delivery uses the same JSON envelope:
Example delivery
Payloads are thin references — they never contain a resource snapshot. Fetch the resource’s current state from the API using data.id (for example, GET /v1/payables/{id}). A delivery that arrives late or out of order still leads you to the latest state.
Events are dispatched in emission order on a best-effort basis, but retries and concurrent delivery mean arrival order is not guaranteed. Order events by sequence (or createdAt), never by arrival.

Event types

Subscribe to individual event types, or to the wildcard * to receive all of them.

Payable events

Bank account events

Contact events


Delivery headers

Every delivery includes these HTTP headers:

Verifying deliveries

Every delivery is signed with HMAC-SHA256 so you can confirm it came from Cleo Pay and was not tampered with in transit. The Cleo-Signature header carries a Unix timestamp and a hex-encoded signature:
The signature is the HMAC-SHA256 of the string {t}.{raw request body} — the timestamp, a literal dot, then the exact body bytes — keyed with your endpoint’s signing secret (whsec_…).
1

Capture the raw request body

Verify against the exact bytes Cleo Pay sent. Do not JSON-parse and re-serialize the body first — any change in key order or whitespace invalidates the signature.
2

Parse the Cleo-Signature header

Extract t (Unix timestamp in seconds) and v1 (hex-encoded signature).
3

Reject stale timestamps

If t is more than 5 minutes from the current time, reject the request. This bounds replay attacks. The timestamp is computed fresh for every delivery attempt, so legitimate retries always carry a current t.
4

Recompute and compare

Compute the HMAC-SHA256 of {t}.{rawBody} with your signing secret and compare it to v1 using a constant-time comparison.

Test your implementation

Use this known-good vector to unit-test the HMAC computation (bypass the timestamp-freshness check for the fixed t):

Signing secrets

  • Each endpoint has its own secret, prefixed with whsec_ (32 bytes of entropy).
  • The full secret is shown once, when the endpoint is created or its secret is rotated. Only the last 4 characters are visible afterwards.
  • Rotation takes effect immediately, with no overlap window. Deliveries already in flight at rotation time were signed with the old secret and may fail your verification — redeliver them from the dashboard if needed.
Treat your signing secret like a password. If it is ever exposed, rotate it from the dashboard immediately — anyone holding the secret can forge deliveries that pass verification.

Retry behavior

A delivery succeeds only when your endpoint returns a 2xx status within 10 seconds. Anything else — a timeout, a connection failure, or a non-2xx response — is retried:
  • Up to 10 attempts per delivery.
  • Exponential backoff starting at 60 seconds and doubling each attempt (1m, 2m, 4m, …), spanning roughly 8.5 hours in total.
  • The body and event id are identical on every attempt; the signature timestamp is computed fresh per attempt.

Automatic pausing

An endpoint is automatically paused after 20 consecutive failed deliveries, or after failing continuously for 24 hours — whichever comes first. When that happens:
  • The endpoint stops receiving deliveries and shows as Paused in the dashboard.
  • Your business admins are notified by email.
  • Once your endpoint is fixed, re-enable it from the dashboard and redeliver anything you missed from the delivery log.
A manual redelivery reuses the same event id and body — your dedupe logic treats it as the same event — but carries a new Cleo-Webhook-Id and a freshly computed signature.

Endpoint requirements

  • HTTPS onlyhttp:// URLs are rejected.
  • The URL must resolve to a publicly routable address. Localhost, private-network, and internal addresses are rejected.
  • Redirects are not followed — the endpoint must respond directly.
  • Respond within 10 seconds — acknowledge first, process asynchronously.

Best practices

Verify every request

Reject anything that fails signature verification before touching the payload. Your webhook URL is reachable by anyone on the internet — the signature is what makes a request trustworthy.

Deduplicate on the event ID

Retries and redeliveries reuse the same event id. Store processed IDs and skip duplicates instead of processing them twice.

Respond fast, process async

Return 2xx immediately and hand the event to a queue or background job. Slow handlers hit the 10-second timeout and trigger retries.

Don't rely on delivery order

Events can arrive out of order. Order by sequence, and fetch the resource’s current state from the API rather than reconstructing it from events.