Webhooks · 5 event types

Webhooks en tiempo real,
firmados con HMAC-SHA256.

5 eventos cubren todo el ciclo de un payment intent. Cada uno llega firmado con HMAC-SHA256 al endpoint que configuraste en el dashboard, con retry exponencial hasta 6 intentos.

5

Event types

HMAC-256

Firma

6 retries

Backoff exponencial

≤ 60s

Latencia típica

Verificación HMAC-SHA256

Cada request trae X-CWallet-Signature = HMAC-SHA256(raw body, webhook_secret). Validá antes de hacer cualquier cosa.

// Node.js + Express (raw body required)
import crypto from 'node:crypto';

app.post('/webhooks/cwallet',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['x-cwallet-signature'];
    const expected = crypto
      .createHmac('sha256', process.env.CWALLET_WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (sig.length !== expected.length ||
        !crypto.timingSafeEqual(
          Buffer.from(sig, 'hex'),
          Buffer.from(expected, 'hex'))) {
      return res.status(401).end();
    }

    const event = JSON.parse(req.body.toString());
    // … process event …
    res.json({ received: true });
  });

En Python: hmac.new(secret, body, hashlib.sha256).hexdigest() con hmac.compare_digest. En PHP: hash_hmac('sha256', $body, $secret) con hash_equals.

Catálogo de eventos

Cada evento llega con headers X-CWallet-Event, X-CWallet-Environment, X-CWallet-Livemode, X-CWallet-Signature.

payment.success

Cuándo: El cliente pagó (live: USDT settled on-chain; sandbox: simulado).

Tu acción: Marcá la orden como pagada. Acreditá producto/servicio al cliente.

{
  "event": "payment.success",
  "intent_id": "11111111-2222-3333-4444-555555555555",
  "transfer_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "amount": 25.5,
  "currency": "USDT",
  "terminal_id": "99999999-aaaa-bbbb-cccc-dddddddddddd",
  "terminal_name": "Caja principal",
  "environment": "live",
  "livemode": true,
  "simulated": false,
  "timestamp": "2026-05-20T14:23:01.234Z"
}

intent.created

Cuándo: Se crea un nuevo payment intent (útil para sincronizar dashboards).

Tu acción: Logueá en tu sistema; opcional para auditoría/analytics.

{
  "event": "intent.created",
  "intent_id": "11111111-2222-3333-4444-555555555555",
  "amount": 25.5,
  "currency": "USDT",
  "terminal_id": "99999999-aaaa-bbbb-cccc-dddddddddddd",
  "terminal_name": "Caja principal",
  "environment": "live",
  "livemode": true,
  "expires_at": "2026-05-20T15:23:01.234Z",
  "checkout_uri": "cwallet:pay?intent=11111111-2222-3333-4444-555555555555",
  "timestamp": "2026-05-20T14:23:01.234Z"
}

payment.expired

Cuándo: Un intent pendiente llegó a su expires_at sin pagarse.

Tu acción: Cancelá el carrito del cliente, liberá stock reservado.

{
  "event": "payment.expired",
  "intent_id": "11111111-2222-3333-4444-555555555555",
  "amount": 25.5,
  "currency": "USDT",
  "terminal_id": "99999999-aaaa-bbbb-cccc-dddddddddddd",
  "terminal_name": "Caja principal",
  "environment": "live",
  "livemode": true,
  "timestamp": "2026-05-20T15:23:01.234Z"
}

key.rotated

Cuándo: Un admin del merchant rotó la API key del terminal desde el dashboard.

Tu acción: Invalidá la API key cacheada — la nueva tenés que sacarla del dashboard manualmente.

{
  "event": "key.rotated",
  "terminal_id": "99999999-aaaa-bbbb-cccc-dddddddddddd",
  "environment": "live",
  "livemode": true,
  "rotated_at": "2026-05-20T16:00:00.000Z",
  "last4": "Ab12"
}

test.ping

Cuándo: Disparado a demanda desde el dashboard ("Probar webhook"). Útil para validar tu endpoint.

Tu acción: Responder 200. Sirve para confirmar que la URL es alcanzable y la firma se verifica bien.

{
  "event": "test.ping",
  "message": "Hello from CWallet. Si ves esto con una firma HMAC válida, tu endpoint está bien wireado.",
  "request_id": "cccccccc-dddd-eeee-ffff-000000000000",
  "environment": "sandbox",
  "livemode": false,
  "timestamp": "2026-05-20T16:05:00.000Z"
}

Política de retries

Si tu endpoint no responde 2xx en 8 segundos, CWallet reintenta con backoff exponencial. Tras 6 intentos fallidos, el evento queda marcado como dead y aparece en /admin para revisión.

IntentoEspera desde el último intentoTotal transcurrido
#1inmediato0s
#2+1 min1 min
#3+5 min6 min
#4+30 min36 min
#5+2 h~3 h
#6+6 h~9 h
dead+24 h~33 h

Preguntas frecuentes

¿Cómo verifico la firma del webhook?▾
Cada POST trae el header X-CWallet-Signature con el HMAC-SHA256 del raw body usando tu webhook_secret. Recalculá lo mismo en tu endpoint y compará con timing-safe equal (crypto.timingSafeEqual en Node, hmac.compare_digest en Python). Si no matchea, devolvé 401 y descartá la request.
¿Qué pasa si mi endpoint responde con error?▾
CWallet reintenta con backoff exponencial: 1m, 5m, 30m, 2h, 6h, 24h. Después de 6 intentos fallidos el evento se marca como dead y queda en /admin para revisión manual. Tu endpoint sólo necesita responder con un status code 2xx para confirmar recepción.
¿Puedo distinguir eventos de sandbox y live en el mismo endpoint?▾
Sí. Cada evento trae environment ("sandbox" | "live") y livemode (boolean) en el body, y los headers X-CWallet-Environment y X-CWallet-Livemode también lo indican. Recomendación: usá URLs distintas (una para sandbox, una para live) configuradas en el dashboard. CWallet rutea automáticamente.
¿Los webhooks pueden llegar fuera de orden?▾
Sí, pueden. Los eventos son entregados con best-effort pero no garantizamos orden estricto. Si necesitás ordenar (ej. payment.expired antes de payment.success), usá el campo timestamp del payload y reordená vos. También es buena idea idempotencia por intent_id: si ya marcaste el intent como pagado, ignorá events posteriores.
¿Cuántos eventos puedo recibir por segundo?▾
No hay rate limit explícito sobre vos como receiver — CWallet entrega los eventos a medida que se generan (que en práctica son ≤ 10 por segundo incluso bajo carga alta). Tu endpoint debe poder manejar bursts cortos y responder en ≤ 8 segundos (timeout del dispatcher).
¿Puedo replay eventos anteriores?▾
Hoy no exponemos un endpoint público para replay. Si necesitás recuperar un evento, contactá soporte con tu merchant_id y el rango temporal. En el roadmap está agregar /admin/webhooks/replay self-service.
¿Cómo testeo localhost desde el dashboard?▾
Usá ngrok (https://ngrok.com) o cloudflared tunnel para exponer tu localhost. Pegá la URL https://abc.ngrok.io en el campo Webhook URL del dashboard Sandbox y dale "Probar webhook" — vas a ver el POST llegando con la firma HMAC válida.
¿En qué orden tengo que validar las cosas?▾
Verificar firma HMAC PRIMERO (con el raw body, no el JSON parsed). Si la firma es inválida → 401 e ignorá. Si es válida → parseá el JSON, idempotencia por intent_id, y recién ahí ejecutá la lógica de negocio. Nunca confíes en el body sin validar la firma.
¿Qué timeout debe tener mi endpoint?▾
CWallet espera máximo 8 segundos por respuesta. Si tu lógica tarda más, devolvé 200 inmediatamente y procesá en background. Patrón típico: enqueue el evento en una cola (Redis, SQS), respondé 200, después un worker lo procesa. Evita procesamiento síncrono pesado en el handler del webhook.
¿Puedo recibir webhooks de varios terminals en el mismo endpoint?▾
Sí. El payload incluye terminal_id y terminal_name para que distingas. Si tenés múltiples terminals (ej. cajas distintas de tu comercio), pueden todos apuntar al mismo webhook URL — vos diferenciás por terminal_id en tu código.