Docs/Webhooks

Webhooks

Receba notificações em tempo real quando os pagamentos são concluídos, falham ou estão pendentes.

Visão Geral

Os Webhooks permitem ao seu servidor receber notificações automáticas HTTP POST quando ocorrem eventos na sua conta Fivo. Em vez de consultar a API, registe um URL e o Fivo envia-lhe os dados do evento assim que acontecem.

Cada pedido de webhook é assinado com HMAC-SHA256 para que possa verificar que veio do Fivo.

Configuração

Configure webhooks a partir da sua página :Painel → Webhooks

  1. Clique em Adicionar Webhook
  2. Introduza o URL do seu endpoint (deve ser HTTPS)
  3. Selecione os eventos que pretende subscrever
  4. Guarde: o Fivo gera um segredo de assinatura para si
!

Requisitos

  • O URL deve usar HTTPS
  • O URL não pode apontar para endereços IP privados/internos (proteção SSRF)
  • O seu endpoint deve responder com um código 2xx em até 5 segundos

Eventos

EventoDescrição
payment.completedO pagamento foi confirmado on-chain e creditado na sua wallet
payment.failedO pagamento falhou (transação revertida ou expirada)
refund.createdUm reembolso foi criado para um pagamento
refund.completedO reembolso foi concluído on-chain e enviado ao cliente
refund.failedO reembolso falhou (verifique failure_reason nos dados)

Payload

Cada webhook envia um payload JSON com o tipo de evento, um timestamp e os dados do evento:

Eventos de Pagamento

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 dos Dados de Pagamento

objeto data (eventos de pagamento)

payment_idstringRequired

ID de pagamento Fivo

amountstringRequired

Montante do pagamento (string decimal)

currencystringRequired

Símbolo do token: USDC ou EURC

statusstringRequired

Estado do pagamento: pending, completed ou failed

tx_hashstring | nullRequired

Hash da transação on-chain

from_addressstring | nullRequired

Endereço da wallet do pagador

to_addressstring | nullRequired

Endereço da wallet do comerciante

source_chainstring | nullRequired

Blockchain de origem (ex.: ETH, BASE)

destination_chainstring | nullRequired

Blockchain de destino (null para mesma cadeia)

is_cross_chainbooleanRequired

Se o pagamento foi transferido entre cadeias

merchant_transfer_tx_hashstringOptional

Hash da transação da transferência para a wallet do comerciante (apenas entre cadeias)

customer_emailstring | nullOptional

Email do cliente, se fornecido durante o pagamento

referencestring | nullOptional

Referência do comerciante (ex.: ID do pedido)

metadataobject | nullOptional

Metadados personalizados associados ao pagamento

Cabeçalhos do Pedido

O Fivo inclui estes cabeçalhos em cada pedido de webhook:

Cabeçalhos

X-Fivo-SignaturestringRequired

Assinatura HMAC-SHA256 com prefixo sha256= (ver Verificação de Assinaturas)

X-Fivo-EventstringRequired

Tipo de evento (ex.: payment.completed)

X-Fivo-TimestampstringRequired

Timestamp Unix em segundos de quando o pedido foi enviado

X-Fivo-TeststringOptional

Definido como "true" para webhooks de teste enviados a partir do painel

Verificação de Assinaturas

Verifique sempre o cabeçalho para confirmar que o pedido veio do Fivo. A assinatura é calculada como usando o seu segredo de webhook como chave, com o prefixo .X-Fivo-SignatureHMAC-SHA256(timestamp + "." + body)sha256=.

O seu segredo de webhook tem este formato: 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

Comparação timing-safe

Use sempre em vez de para prevenir ataques de temporização.crypto.timingSafeEqual()===

Tentativas e Desativação Automática

Se o seu endpoint devolver um código diferente de 2xx ou expirar, o Fivo incrementa um contador de falhas. Após falhas consecutivas, o webhook é automaticamente desativado para evitar mais entregas falhadas.10

Pode reativar um webhook desativado a partir do painel a qualquer momento. O contador de falhas é reiniciado com qualquer entrega bem-sucedida.

Testes

Use o botão na sua página de para enviar um evento de teste para o seu endpoint. Os webhooks de teste incluem o cabeçalho para que o seu servidor possa distingui-los dos eventos reais.Enviar testeWebhooksX-Fivo-Test: true

Os registos de entrega são visíveis na página de detalhes do webhook: pode inspecionar o payload, o código de resposta e o tempo de resposta de cada entrega.

Exemplo 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);