Realfy Realfy Docs Painel

Recursos

Webhooks

Notificações em tempo real quando algo acontece. Realfy envia POST pro seu endpoint sempre que um pagamento muda de status, com assinatura HMAC pra você verificar autenticidade.

Setup

  1. Painel → WebhooksNovo endpoint
  2. Informa a URL HTTPS pública do seu sistema (ex: https://app.com/webhooks/realfy)
  3. Copia o secret gerado e guarda no env do seu app
  4. Pronto — eventos novos já começam a ser entregues

Eventos disponíveis

EventoQuando dispara
deposit.createdCobrança PIX criada (QR Code disponível)
deposit.paidCobrança paga — saldo já caiu na sua conta
deposit.expiredQR Code expirou sem pagamento
withdrawal.createdSaque PIX criado, aguardando processamento
withdrawal.processingProvedor BaaS começou a processar
withdrawal.completedPIX entregue no destinatário
withdrawal.failedPIX falhou — saldo já estornado

Formato do payload

POST https://seu-site.com/webhooks/realfy
X-Realfy-Signature: t=1779800000,v1=5257a869e7ecebeda...
X-Realfy-Event: deposit.paid
X-Realfy-Delivery-Id: del_xyz123
Content-Type: application/json

{
  "event": "deposit.paid",
  "data": {
    "id": "dep_4054",
    "amount": 50.00,
    "netAmount": 49.25,
    "status": "paid",
    "reference": "order-001",
    "createdAt": "2026-05-26T13:30:00Z",
    "paidAt": "2026-05-26T13:31:42Z"
  }
}

Verificar a assinatura

Sempre verifique a assinatura

Sem verificar, qualquer um pode forjar requests fingindo ser de Realfy e marcar pedidos como pagos no seu sistema. Nunca processe o evento sem validar primeiro.

A assinatura usa HMAC-SHA256 do timestamp + body, com o secret do endpoint. Pseudocódigo:

expected = HMAC-SHA256(secret, "${timestamp}.${rawBody}").hex()
if (expected !== signatureHeader.v1) → rejeite
if (now - timestamp > 5min) → rejeite (replay attack)

Implementação na sua linguagem:

import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyWebhook(rawBody, signatureHeader, secret) {
  // Parse header "t=1779800000,v1=abc123..."
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.split('='))
  );

  // 1. Reconstrói o HMAC esperado
  const signedPayload = `${parts.t}.${rawBody}`;
  const expected = createHmac('sha256', secret).update(signedPayload).digest('hex');

  // 2. Compara em tempo constante (evita timing attack)
  const expectedBuf = Buffer.from(expected, 'hex');
  const receivedBuf = Buffer.from(parts.v1, 'hex');
  if (expectedBuf.length !== receivedBuf.length) {
    throw new Error('Invalid signature');
  }
  if (!timingSafeEqual(expectedBuf, receivedBuf)) {
    throw new Error('Invalid signature');
  }

  // 3. Verifica janela de 5min (anti-replay)
  const timestampMs = Number(parts.t) * 1000;
  if (Date.now() - timestampMs > 5 * 60 * 1000) {
    throw new Error('Webhook expired (replay attack?)');
  }
}

// No Express:
app.post('/webhooks/realfy', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    verifyWebhook(
      req.body.toString(),
      req.headers['x-realfy-signature'],
      process.env.WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString());
  console.log('Webhook validated:', event.event, event.data);
  res.status(200).send('OK');
});

Use o raw body

A assinatura é calculada sobre o body cru (bytes), antes de qualquer parsing JSON. Se você der JSON.parse e depois re-stringificar, a ordem dos campos pode mudar e a assinatura falha. Sempre intercepta o body raw (Express: express.raw(), Fastify: contentTypeParser).

Retry policy

Se seu endpoint retornar não-2xx ou timeout (5s), reenviamos automaticamente:

TentativaDelay desde criação
1ª (inicial)imediato
+1 min
+5 min
+30 min
5ª (final)+1 h

Após 5 tentativas, o delivery vira failed e você pode reenviar manualmente pelo painel.

Boas práticas

  • Responda rápido — 2xx em < 1s. Se precisar processar, enfileira um job no seu lado.
  • Seja idempotente — o mesmo evento pode chegar 2x (retry após network flap). Use X-Realfy-Delivery-Id como dedup.
  • Loga tudo — guarde o body e a assinatura recebida pra investigar disputas.
  • HTTPS sempre — não aceitamos URLs HTTP.