Skip to main content
Webhooks notify you in real time, on your own server, every time a payment changes state — no polling required. 1to1 sends a signed POST to the URL you register, with the payment details.
These webhooks are outbound (1to1 → your server). You don’t call them: you receive them. Every delivery is signed (the Standard Webhooks spec) so you can verify it came from us and wasn’t tampered with.

Set up an endpoint

In the dashboard, under Settings → API and Connections, add your system’s URL and pick the events you want to receive. On creation you get a signing secret (whsec_…) shown only once — store it as a secret: it’s the key you verify each webhook with. You can register up to 10 endpoints per business.
The URL must be https:// and public. Keep the whsec_… in a safe place on the server (environment variable, secret manager) — never in client code or the repository. If you lose it, rotate the secret from the dashboard.

Event catalog

payment.failed covers only manual payments marked failed by your team. Online payment failures (card declined, expired session) do not emit a webhook.

The payload

Each POST carries a JSON body with this shape. The example is a credited manual payment:
event_at is when the payment event happened (for payment.failed, the time of the failure). It’s different from the webhook-timestamp header, which is the send time used for the anti-replay signature.
Some fields are manual-payment only (provider: "manual"): bank_account (the label of the account the money went into, e.g. "BANORTE 0876"), bank_datetime (bank date/time, optional), and files (uploaded receipts). On online payments they’re null / []. For card and online SPEI use transaction_id and receipt_url.
conversation_info.uuid can be null if the associated conversation was deleted — the payment still notifies (the webhook is about the payment’s lifecycle, not the conversation’s). Always correlate by data.payment.uuid.

Headers on every delivery

string
Unique delivery id. Stable across retries of the same event — use it to deduplicate (see Delivery).
integer
Send time (Unix seconds). Recomputed on each retry and part of the signature. Reject any outside a reasonable window (±5 min) to protect against replays.
string
One or more v1,<base64> signatures separated by a space (there’ll be two during a secret rotation). The delivery is valid if any of them matches.
string
The event type (payment.credited, payment.failed, …). Matches type in the body.
string
Always 1to1-Webhooks/1.0.

Verify the signature

The signature is an HMAC-SHA256 of the content {webhook-id}.{webhook-timestamp}.{body}, where body is the exact raw body you received (don’t re-serialize it). The key is your whsec_… with the prefix removed and the rest base64-decoded.
Verify against the raw body, before parsing the JSON. Re-serializing the object changes bytes (whitespace, key order) and breaks the signature.
The simplest way is the official Standard Webhooks library, which handles rotation and constant-time comparison for you:
If you’d rather verify by hand (no dependencies), replicate the HMAC and compare in constant time against each signature in the header:

Delivery and retries

1

Respond 2xx fast

Reply with any 2xx as soon as you receive the webhook. If you take longer than 10 seconds or respond with another status, we treat it as a failure and retry.
2

Deduplicate by webhook-id

Delivery is at-least-once: the same event may arrive more than once (a retry after a timeout, for example). The webhook-id is stable across retries — store it and discard duplicates.
3

Don't assume order

Events are not guaranteed to arrive in order. Use data.payment.uuid + status as the truth, not arrival order: a late payment.registered must not override a payment.credited you already processed.
4

Retries ~3 days

A down endpoint gets retries with growing spacing over ~3 days. If it keeps failing, the subscription is disabled automatically (after 5 consecutive exhausted failures) — you re-enable it from the dashboard.

Rotate the secret

You can rotate the signing secret anytime from the dashboard. During a 24-hour grace period, each webhook is signed with both the new and the previous secret (two signatures in the header). This lets you update your system without losing or rejecting events: validate against either one; when the 24-hour grace ends, the previous secret automatically stops signing.

Receipts

On a manual payment, files[] lists the uploaded receipts with stable references — uuid, name, and mime_type — but without a download URL: the payload is a snapshot retried for days, and a signed URL would expire along the way. Downloading the file is done with your API key through an authenticated endpoint (documented here once available); in the meantime, the uuid lets you correlate the receipt in the dashboard.

Next steps

Authentication

How your integration authenticates with the API key.

Errors

The public API’s error contract.