Guías
Webhooks
Avisamos a tu servidor con un POST firmado cada vez que cambia una factura, para que completes los pedidos automáticamente.
La petición
POST /webhooks/crypto HTTP/1.1
Content-Type: application/json
User-Agent: CryptoPay-Webhooks/1.0
X-Webhook-Id: whd_01J9ZQ…
X-Webhook-Event: invoice.paid
X-Webhook-Timestamp: 1760097871
X-Webhook-Signature: t=1760097871,v1=5f8a…e21c
{
"id": "evt_01J9ZQ3K5W8X2N4M6P7R9S1T3V",
"object": "event",
"type": "invoice.paid",
"api_version": "2026-10-01",
"livemode": true,
"created_at": "2026-10-10T12:04:31.000Z",
"data": {
"object": {
"id": "7d61d9f9-a9e4-49c1-a1b8-b5d465d7a939",
"object": "invoice",
"status": "paid",
"order_id": "SO-1001",
"amount": {
"value": "25.00",
"currency": "USD",
"minor": 2500
},
"…": "…"
}
}
}data.object es la factura tal como la devuelve la API. Los eventos de pago incluyen además data.payment. Responde con cualquier código 2xx en menos de 10 segundos; haz el trabajo lento después de responder.
Verifica la firma
Calcula un HMAC-SHA256 de "<t>.<raw body>" con el secreto de tu endpoint (whsec_…) y compáralo en tiempo constante con v1. Rechaza la petición si t se aleja más de 5 minutos de tu reloj: así se bloquean reenvíos de peticiones antiguas.
import crypto from 'node:crypto'
import express from 'express'
const app = express()
const SECRET = process.env.WEBHOOK_SECRET // whsec_…
export function verifyWebhookSignature(secret, header, rawBody, toleranceS = 300) {
const parts = Object.fromEntries(
(header ?? '').split(',').map((p) => p.trim().split('=')),
)
const ts = Number(parts.t)
if (!ts || !parts.v1 || Math.abs(Date.now() / 1000 - ts) > toleranceS) return false
const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')
const a = Buffer.from(expected, 'hex')
const b = Buffer.from(parts.v1, 'hex')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
// Use the RAW body: re-serialising the JSON breaks the signature.
app.post('/webhooks/crypto', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8')
if (!verifyWebhookSignature(SECRET, req.get('X-Webhook-Signature'), raw)) return res.sendStatus(400)
const event = JSON.parse(raw)
if (event.type === 'invoice.paid') {
// Mark event.data.object.order_id as paid (idempotently: you may get the same event twice).
}
res.sendStatus(200)
})Reintentos
Si tu servidor no responde 2xx (o tarda más de 10 s) reintentamos hasta 8 veces: de inmediato → 1 minuto → 5 minutos → 30 minutos → 2 horas → 6 horas → 12 horas → 24 horas. Después, la entrega se marca como fallida y, tras 3 eventos fallidos seguidos, el dueño del workspace recibe un email. Puedes reenviar cualquier entrega desde el panel.
Las entregas pueden llegar más de una vez o desordenadas: procésalas de forma idempotente usando el id del evento y lee el status actual de la factura.
Tipos de evento
invoice.createdSe creó una factura.invoice.pendingSe eligió una moneda: la dirección y el importe quedan fijados.invoice.partialLlegó parte del importe.invoice.confirmingLlegó el importe completo y espera confirmaciones.invoice.paidPagada y confirmada: completa el pedido.invoice.overpaidPagada con más de lo pedido.invoice.expiredCaducó sin el importe completo.invoice.late_paymentLlegó un pago después de que la factura caducara.invoice.cancelledLa factura se canceló.payment.detectedSe vio en la cadena una transferencia a la dirección de la factura (data.payment).payment.confirmedEsa transferencia alcanzó las confirmaciones necesarias.webhook.testEvento de prueba enviado desde el panel.
Reglas de los endpoints
Las URL deben ser https:// públicas; se rechazan las direcciones privadas y locales. Para desarrollo local crea un endpoint de prueba con http://localhost:PUERTO. Cada endpoint recibe eventos live, de prueba o ambos, y puedes suscribirte a todos los eventos o solo a algunos.