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.
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
EachPOST 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.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.
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.
The simplest way is the official Standard Webhooks library, which handles
rotation and constant-time comparison for you:
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.