Skip to main content

Webhooks Salientes

CUCU entrega eventos a tu URL registrada usando el estándar Standard Webhooks para que la verificación de firma sea sencilla y predecible.

Headers en cada entrega

HeaderDescripción
webhook-idUUID único de esta entrega. Úsalo como delivery_id para idempotencia en tu receptor.
webhook-timestampUNIX timestamp del momento del envío.
webhook-signatureFirma de autenticidad. Formato: v1,<base64(HMAC-SHA256)>.
webhook-eventTipo de evento (payment.confirmed, payment.failed, etc.).

Verificación de firma

mensaje_a_firmar = "{webhook-id}.{webhook-timestamp}.{raw_body_bytes}"
firma_esperada   = base64( HMAC-SHA256(mensaje_a_firmar, tu_webhook_secret) )
Compara firma_esperada con el valor en webhook-signature (después del prefijo v1,). Ejemplo en Python:
import base64
import hashlib
import hmac

def verificar_webhook_cucu(
    body: bytes,
    webhook_id: str,
    webhook_timestamp: str,
    webhook_signature: str,
    secret: str,
) -> bool:
    mensaje = f"{webhook_id}.{webhook_timestamp}.{body.decode()}"
    firma = base64.b64encode(
        hmac.new(secret.encode(), mensaje.encode(), hashlib.sha256).digest()
    ).decode()
    esperada = webhook_signature.removeprefix("v1,")
    return hmac.compare_digest(firma, esperada)
Ejemplo en Node.js:
const crypto = require('crypto');

function verificarWebhookCucu({ body, webhookId, webhookTimestamp, webhookSignature, secret }) {
  const mensaje = `${webhookId}.${webhookTimestamp}.${body}`;
  const firma = crypto
    .createHmac('sha256', secret)
    .update(mensaje)
    .digest('base64');
  const esperada = webhookSignature.replace('v1,', '');
  return crypto.timingSafeEqual(Buffer.from(firma), Buffer.from(esperada));
}

Eventos

payment.confirmed

El pago fue recibido y confirmado en la blockchain. Acredita el pedido en tu sistema.
{
  "event": "payment.confirmed",
  "occurred_at": "2026-01-25T15:22:45Z",
  "charge_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "CHG-20260125-00042",
  "provider": "cucu_pay",
  "amount": "50.00",
  "currency": "USDT",
  "status": "completed",
  "metadata": {
    "user_id": "usr_abc123",
    "plan": "pro",
    "invoice_id": "inv_0042"
  }
}

payment.failed

El pago fue detectado pero no pudo procesarse (monto incorrecto, error de red, etc.).
{
  "event": "payment.failed",
  "occurred_at": "2026-01-25T15:23:10Z",
  "charge_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "CHG-20260125-00042",
  "provider": "cucu_pay",
  "amount": "50.00",
  "currency": "USDT",
  "status": "failed",
  "error": "amount_mismatch",
  "metadata": {}
}

charge.expired

El usuario no pagó dentro del tiempo de expiración del cobro.
{
  "event": "charge.expired",
  "occurred_at": "2026-01-25T15:30:00Z",
  "charge_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "CHG-20260125-00042",
  "status": "expired",
  "metadata": {}
}

charge.cancelled

El cobro fue cancelado programáticamente (via POST /cancel).
{
  "event": "charge.cancelled",
  "occurred_at": "2026-01-25T15:20:00Z",
  "charge_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "CHG-20260125-00042",
  "status": "cancelled",
  "metadata": {}
}

webhook.test

Evento sintético generado al llamar POST /merchants/me/webhook/test. No representa un pago real.
{
  "event": "webhook.test",
  "occurred_at": "2026-01-25T14:00:00Z",
  "merchant_id": "392b8825-18d3-404f-a029-2f11c8d14d32",
  "delivery_id": "d4e5f6a7-b8c9-0123-defa-456789abcdef"
}

Política de reintentos

IntentoDelay
1 (inmediato)0s
2~30s
3~2min
4~8min
5~30min
6~2h
Si los 6 intentos fallan, la entrega queda en estado failed y puedes consultarla en tu dashboard o contactar soporte CUCU. Tu endpoint debe responder 2xx para que CUCU considere la entrega exitosa. Cualquier otro código HTTP (incluyendo 3xx y 5xx) activa el siguiente reintento.