CryptoPay
Obtener claves de API

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

HTTP
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)
})
Verifica sobre los bytes crudos del cuerpo. Los frameworks que parsean y vuelven a serializar el JSON cambian espacios u orden de claves y la firma deja de coincidir.

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.