Docs/Webhooks

Webhooks

Reciba notificaciones en tiempo real cuando los pagos se completen, fallen o estén pendientes.

Descripción General

Los Webhooks permiten que su servidor reciba notificaciones HTTP POST automáticas cuando ocurren eventos en su cuenta de Fivo. En lugar de consultar la API periódicamente, usted registra una URL y Fivo le envía los datos del evento a medida que ocurren.

Cada solicitud de webhook está firmada con HMAC-SHA256 para que pueda verificar que proviene de Fivo.

Configuración

Configure los webhooks desde su página de :Panel → Webhooks

  1. Pulse Añadir Webhook
  2. Introduzca la URL de su endpoint (debe ser HTTPS)
  3. Seleccione a qué eventos desea suscribirse
  4. Guarde: Fivo genera un secreto de firma para usted
!

Requisitos

  • La URL debe usar HTTPS
  • La URL no puede apuntar a direcciones IP privadas/internas (protección SSRF)
  • Su endpoint debe responder con un código 2xx en un plazo de 5 segundos

Eventos

EventoDescripción
payment.completedEl pago fue confirmado on-chain y acreditado en su wallet
payment.failedEl pago falló (transacción revertida o expirada)
refund.createdSe ha creado un reembolso para un pago
refund.completedReembolso completado on-chain y enviado al cliente
refund.failedEl reembolso falló (consulte failure_reason en data)

Carga Útil

Cada webhook envía una carga útil JSON con el tipo de evento, una marca de tiempo y los datos del evento:

Eventos de Pago

payment.completed payload
{
  "event": "payment.completed",
  "timestamp": "2026-03-04T10:30:45.123Z",
  "data": {
    "payment_id": "fivo_live_abc123",
    "amount": "100.00",
    "currency": "USDC",
    "status": "completed",
    "tx_hash": "0xabc...def",
    "from_address": "0x1234...5678",
    "to_address": "0xabcd...ef01",
    "source_chain": "ETH",
    "destination_chain": "BASE",
    "is_cross_chain": true,
    "merchant_transfer_tx_hash": "0x9876...5432",
    "customer_email": "customer@example.com",
    "reference": "ORD-2026-001",
    "metadata": { "product_id": "plan_pro" }
  }
}

Eventos de Reembolso

refund.completed payload
{
  "event": "refund.completed",
  "timestamp": "2026-03-04T10:35:20.456Z",
  "data": {
    "refund_id": "refund-uuid",
    "payment_id": "payment-uuid",
    "amount": "49.99",
    "currency": "USDC",
    "status": "completed",
    "destination_address": "0xCustomer...",
    "blockchain": "BASE",
    "reason": "requested_by_customer",
    "tx_hash": "0xabc..."
  }
}

Campos de Datos de Pago

objeto data (eventos de pago)

payment_idstringRequired

ID de pago de Fivo

amountstringRequired

Monto del pago (cadena decimal)

currencystringRequired

Símbolo del token: USDC o EURC

statusstringRequired

Estado del pago: pending, completed o failed

tx_hashstring | nullRequired

Hash de la transacción on-chain

from_addressstring | nullRequired

Dirección de la wallet del pagador

to_addressstring | nullRequired

Dirección de la wallet del comerciante

source_chainstring | nullRequired

Blockchain de origen (ej. ETH, BASE)

destination_chainstring | nullRequired

Blockchain de destino (null para la misma cadena)

is_cross_chainbooleanRequired

Si el pago fue transferido entre cadenas

merchant_transfer_tx_hashstringOptional

Hash TX de la transferencia a la wallet del comerciante (solo entre cadenas)

customer_emailstring | nullOptional

Correo electrónico del cliente si se proporcionó durante el pago

referencestring | nullOptional

Referencia del comerciante (ej. ID de pedido)

metadataobject | nullOptional

Metadatos personalizados adjuntos al pago

Cabeceras de la Solicitud

Fivo incluye estas cabeceras en cada solicitud de webhook:

Cabeceras

X-Fivo-SignaturestringRequired

Firma HMAC-SHA256 con prefijo sha256= (consulte Verificación de Firmas)

X-Fivo-EventstringRequired

Tipo de evento (ej. payment.completed)

X-Fivo-TimestampstringRequired

Marca de tiempo Unix en segundos del momento en que se envió la solicitud

X-Fivo-TeststringOptional

Se establece como "true" para webhooks de prueba enviados desde el panel

Verificación de Firmas

Verifique siempre la cabecera para confirmar que la solicitud proviene de Fivo. La firma se calcula como usando su secreto de webhook como clave, con el prefijo .X-Fivo-SignatureHMAC-SHA256(timestamp + "." + body)sha256=.

Su secreto de webhook tiene este aspecto: whsec_a1b2c3d4e5f6...

verify-webhook.js
const crypto = require('crypto');

function verifyWebhook(req, secret) {
  const signature = req.headers['x-fivo-signature'];
  const timestamp = req.headers['x-fivo-timestamp'];
  const body = JSON.stringify(req.body);

  // 1. Reject if timestamp is older than 5 minutes (replay protection)
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 5 * 60) {
    throw new Error('Webhook timestamp too old');
  }

  // 2. Compute expected signature: HMAC-SHA256(timestamp + "." + body)
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + body)
    .digest('hex');

  // 3. Compare using timing-safe comparison
  if (signature.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    throw new Error('Invalid webhook signature');
  }

  return JSON.parse(body);
}
i

Comparación segura en tiempo

Use siempre en lugar de para prevenir ataques de temporización.crypto.timingSafeEqual()===

Reintentos y Desactivación Automática

Si su endpoint devuelve un código no-2xx o se agota el tiempo, Fivo incrementa un contador de fallos. Tras fallos consecutivos, el webhook se desactiva automáticamente para evitar más entregas fallidas.10

Puede reactivar un webhook desactivado desde el panel en cualquier momento. El contador de fallos se reinicia con cualquier entrega exitosa.

Pruebas

Use el botón en su página de para enviar un evento de prueba a su endpoint. Los webhooks de prueba incluyen la cabecera para que su servidor pueda distinguirlos de los eventos reales.Enviar pruebaWebhooksX-Fivo-Test: true

Los registros de entrega son visibles en la página de detalle del webhook: puede inspeccionar la carga útil, el código de respuesta y el tiempo de respuesta de cada entrega.

Ejemplo Completo (Express)

server.js
const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.FIVO_WEBHOOK_SECRET;

app.post('/webhooks/fivo', (req, res) => {
  try {
    // 1. Verify signature
    const signature = req.headers['x-fivo-signature'];
    const timestamp = req.headers['x-fivo-timestamp'];
    const body = JSON.stringify(req.body);

    const expected = 'sha256=' + crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(timestamp + '.' + body)
      .digest('hex');

    if (signature.length !== expected.length ||
        !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    // 2. Replay protection
    const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
    if (age > 5 * 60) {
      return res.status(401).json({ error: 'Timestamp too old' });
    }

    // 3. Handle event
    const { event, data } = req.body;

    switch (event) {
      case 'payment.completed':
        console.log('Payment received:', data.amount, data.currency);
        console.log('From:', data.from_address, 'on', data.source_chain);
        console.log('Customer:', data.customer_email);
        console.log('Reference:', data.reference);
        // Update your order status, send confirmation, etc.
        break;
      case 'payment.failed':
        console.log('Payment failed:', data.payment_id);
        break;
      case 'refund.created':
        console.log('Refund created:', data.refund_id);
        break;
      case 'refund.completed':
        console.log('Refund completed:', data.refund_id);
        break;
      case 'refund.failed':
        console.log('Refund failed:', data.refund_id);
        break;
    }

    res.status(200).json({ received: true });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.listen(3000);