ES

Webhooks de pagos cripto: verifica la firma, gestiona reintentos y duplicados

Webhooks de SnapEX Pay

Un webhook es la forma en que tu tienda, tu panel de facturación o tu bot se entera de que un cliente pagó. SnapEX Pay envía un HTTP POST a tu URL en cada evento de factura, pago y retiro. Bien hecho, es la señal más rápida y fiable que tienes. Hecho a la ligera, es una forma de marcar como pagados pedidos que nunca se pagaron. Esta guía cubre lo que importa.

Configúralo

En el panel del comercio ve a Invoices → la tarjeta Webhook, escribe tu URL y pulsa Save. Una URL por cuenta recibe todos los tipos de evento. Al guardar por primera vez, el panel muestra una sola vez el secreto de firma whsec_…: guárdalo solo en tu servidor.

La URL debe ser una dirección https:// absoluta con un host público: sin login:password, sin localhost, y el host no debe resolver a una IP privada o reservada. Las redirecciones no se siguen, así que indica la URL final.

Qué llega

Cada petición lleva cuatro encabezados:

Encabezado Significado
Webhook-Id Id único del evento. No cambia en los reintentos ni en los reenvíos manuales.
Webhook-Timestamp Hora Unix (segundos) en que se envió este intento. Cada reintento tiene un valor nuevo.
Webhook-Signature v1= y el HMAC-SHA256 en hex minúsculas de {Webhook-Timestamp}.{raw body}.
Content-Type Siempre application/json.

El cuerpo tiene id (el mismo que Webhook-Id), type, created_at y data. En los eventos invoice.*, data es la factura con la misma forma que GET /invoices/{id}, incluidos environment, amount_usd, status y tu external_id.

Los eventos de factura:

Evento Cuándo
invoice.created Factura creada, todavía sin método de pago elegido.
invoice.awaiting Se eligió un método de pago y se asignó la dirección.
invoice.underpaid Llegó una parte del importe; la misma dirección espera el resto.
invoice.paid Se pagó y acreditó el importe completo. La única señal para entregar el pedido.
invoice.partially_paid Solo se pagó una parte, terminó el plazo para completar el pago y la parte recibida se te acreditó.
invoice.expired Terminó el plazo de la factura sin pago.
invoice.extra_payment Llegó otra transferencia después de pagada la factura; se acredita por separado.

Verifica la firma

  1. Lee el cuerpo crudo de la petición como bytes, antes de cualquier parseo de JSON. Un JSON vuelto a serializar no coincidirá.
  2. Calcula el HMAC-SHA256 de {Webhook-Timestamp}.{raw body} con el secreto completo como clave, prefijo whsec_ incluido, y codifícalo en hex.
  3. Compara v1= + hex con el encabezado Webhook-Signature usando una función de tiempo constante.
  4. Rechaza un Webhook-Timestamp que difiera de tu reloj en más de 5 minutos. Los reintentos llevan una marca de tiempo nueva, así que esto no los rompe.

PHP:

$secret = getenv('SNAPEX_WEBHOOK_SECRET');          // whsec_… from the cabinet
$body   = file_get_contents('php://input');           // raw body, before json_decode
$ts     = $_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
$sig    = $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '';

$expected = 'v1=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (!hash_equals($expected, $sig) || abs(time() - (int) $ts) > 300) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// skip if $event['id'] was already processed; do the heavy work in a queue
http_response_code(200);

Node.js (Express):

const crypto = require('crypto');

app.post('/hooks/snapex', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('Webhook-Timestamp') || '';
  const sig = Buffer.from(req.get('Webhook-Signature') || '');
  const expected = Buffer.from('v1=' + crypto
    .createHmac('sha256', process.env.SNAPEX_WEBHOOK_SECRET)
    .update(ts + '.' + req.body)
    .digest('hex'));

  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  if (!fresh || sig.length !== expected.length || !crypto.timingSafeEqual(sig, expected)) {
    return res.sendStatus(400);
  }

  const event = JSON.parse(req.body);
  // skip if event.id was already processed; queue the heavy work
  res.sendStatus(200);
});

Reintentos, duplicados y orden

  • Éxito es cualquier 2xx. Cualquier otro código, un timeout o un error de red es un fallo. Los timeouts son de 5 segundos para conectar y 15 segundos para toda la petición, así que responde rápido y haz el trabajo pesado de forma asíncrona.
  • Reintentos. Hasta 5 intentos por evento: el primero y luego unos 10 segundos, 1 minuto, 5 minutos y 15 minutos después de cada fallo. Tras el quinto fallo la entrega automática se detiene; el panel lista las entregas recientes y tiene un botón Retry que reenvía el evento con el mismo Webhook-Id.
  • Duplicados. La entrega es al menos una vez (at-least-once): el mismo evento puede llegar más de una vez. Guarda los valores de Webhook-Id ya procesados e ignora las repeticiones.
  • Orden. Los eventos pueden llegar fuera de orden: un evento antiguo reintentado puede llegar después de uno más nuevo. No dependas del orden; compara el estado del evento con el que ya tienes.
  • Vuelve a comprobar si quieres. Después de un webhook puedes volver a leer el objeto con la API: GET /invoices/{id}.

Pruébalo sin dinero real

Crea una factura de prueba ("environment": "test"), págala en la página de pago y mira cómo llegan los webhooks con data.environment = "test". Detalles: Cómo probar pagos cripto sin dinero real.

Los módulos listos para WHMCS, BILLmanager, WooCommerce y otras plataformas ya verifican la firma y nunca acreditan un pago dos veces; consulta pay.snapex.pro. Referencia completa: documentación de la API de SnapEX Pay.

Más guías

Cómo elegir una pasarela de pagos cripto para una empresa de hosting: 8 preguntas Una lista de verificación para proveedores de hosting y VPS que eligen una pasarela de pagos cripto: módulo de facturación, cómo se marcan como pagadas las facturas, pagos incompletos, comisiones, liquidación, monedas, modo de prueba y facturación multimoneda. SnapEX Pay vs NOWPayments, Cryptomus y CoinGate: comisiones y módulos listos comparados Cómo se compara SnapEX Pay con NOWPayments, Cryptomus y CoinGate en la comisión publicada, las cuotas mensuales y los módulos listos para facturación de hosting, paneles VPN y tiendas. Datos de la competencia tomados de sus propios sitios, 11 de octubre de 2026. El cliente envió menos que la factura: cómo funcionan los pagos cripto incompletos Los exchanges suelen descontar su comisión de retiro del importe, así que una factura cripto llega incompleta. Qué hace SnapEX Pay con un pago incompleto, qué webhooks recibes y qué hacer con el pedido. Cómo probar pagos cripto sin dinero real Las facturas de prueba de SnapEX Pay pasan por los mismos estados y envían los mismos webhooks que las reales, pero se confirman solas y nunca tocan tu saldo. Así se usan. Quién paga las comisiones de una factura cripto: tú o tu cliente SnapEX Pay te deja elegir en cada factura quién cubre la comisión del servicio y la comisión de red. Qué hace fee_on y cómo cambia el importe que paga el cliente y el que recibes tú. Renovaciones de suscripciones VPN pagadas con cripto: extiende usuarios sin duplicados ni días perdidos Cómo gestionan los módulos de SnapEX Pay para Marzban, 3x-ui, Remnawave y el bot VPN de Telegram a un cliente que vuelve: desde qué vencimiento cuentan los días nuevos y qué pasa con el tráfico, las configuraciones y los webhooks repetidos.

¿Listo para aceptar cripto?

Crea una cuenta de comercio y obtén una clave API. 1% por pago, sin cuota mensual.