Core
Webhooks & signing
Subscribe to events with HMAC-SHA256 signed deliveries. Pick managed inbox (we host) or your own backend URL.
Subscribe to event types from your dashboard or via the API. Each delivery includes a signed header so you can verify the payload was issued by Key2Pay. Hay dos formas de recibir los eventos — usá la que mejor encaje con tu stack.
Two delivery modes
Managed inbox
Recomendado para empezar
Te asignamos una URL en NUESTRO dominio (
merchant.key2pays.com/api/webhooks/inbox/<tu-shop>). Los eventos llegan ahí y los ves en /dashboard/webhooks sin levantar infraestructura. Es la opción default cuando creás un webhook sin URL en el dashboard.Tu propia URL
Vos hosteás el endpoint (ej.
https://acme.com/webhooks/key2pay) y nosotros le hacemos POST con el evento firmado. Más control, ideal para producción cuando tu backend ya está listo para procesar eventos automáticamente.Path de adopción típico: sandbox arranca con managed inbox para inspeccionar las cargas y ajustar el handler; cuando todo se ve bien, agregás una segunda subscripción con tu URL real y deshabilitás la managed. Las dos pueden coexistir, así que también podés tener producción usando ambas (la managed como audit log de backup, la externa como el handler real).
text
X-Key2Pay-Signature: t=1714672890,v1=2c8a8…b7 X-Key2Pay-Event: payment.completed X-Key2Pay-Delivery: dlv_1zP9e…
Verifying a delivery
javascript
import crypto from "node:crypto";
export function verify(payload, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=")),
);
const t = Number(parts.t);
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${payload}`)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) {
throw new Error("invalid_signature");
}
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) {
throw new Error("stale_timestamp");
}
}Always verify with a constant-time comparison. Reject any delivery whose timestamp is more than 5 minutes old to defend against replay.
Event types
| Event | Fires when… |
|---|---|
payment.completed | Capture succeeded — funds landed in the merchant balance. |
payment.failed | Charge attempt failed or the customer abandoned a pending voucher/PIX. |
payment.refunded | A previously-completed transaction was refunded in full or in part. |
chargeback.created | A dispute was opened against a completed transaction. Funds are frozen. |
claim.opened | Legacy alias for chargeback.created — kept for back-compat. Prefer chargeback.created on new code. |
claim.resolved | Internal claim (refund or chargeback) reached a final resolution. |
settlement.closed | A settlement batch was closed and the crypto payout was initiated. |
settlement.pdf_ready | The PDF receipt for a closed settlement is ready to download. |
withdrawal.completed | A merchant withdrawal finished — funds left the platform. |
withdrawal.failed | A merchant withdrawal failed and the funds were returned to balance. |
payment.captured | Deprecated alias for payment.completed. Subscribe to payment.completed instead. |
Register an endpoint
Vía dashboard — /dashboard/webhooks tiene un wizard con dos opciones (Managed inbox / URL propia). El secret HMAC se muestra una sola vez después de crear; guardalo.
Vía API — POST /api/v1/webhooks:
bash
# Tu propia URL (modo externo):
curl https://sandbox.key2pays.com/api/v1/webhooks \
-H "Authorization: Bearer sk_test_51N8mP...exampleK3Y" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/key2pay",
"events": ["payment.completed", "payment.failed", "payment.refunded"]
}'
# Managed inbox (modo gestionado — omití el campo url):
curl https://sandbox.key2pays.com/api/v1/webhooks \
-H "Authorization: Bearer sk_test_51N8mP...exampleK3Y" \
-H "Content-Type: application/json" \
-d '{
"events": ["*"]
}'
# Respuesta: { "url": "https://sandbox.key2pays.com/api/webhooks/inbox/<shopSlug>",
# "managed": true, "secret": "whsec_…", … }La respuesta incluye
managed: true cuando se autogeneró la URL. El secret se devuelve EXACTAMENTE UNA VEZ — guardalo en tu vault aunque uses managed inbox (el día que migres a tu propia URL podés reutilizarlo).