Webhooks

Refera te avisa quando algo acontece.

Cadastra uma URL no dashboard e escolhe quais eventos quer receber. A gente entrega via POST com corpo JSON + assinatura HMAC-SHA256 no cabeçalho. Cada entrega é persistida em webhook_deliveries, com retry automático em backoff exponencial até 8 tentativas.

Eventos disponíveis

  • partner.created — novo parceiro entrou no programa.
  • sale.created — venda registrada (independente de haver comissão).
  • commission.available — comissão nasceu (via venda ou conversion).
  • conversion.recorded — conversão registrada.
  • withdrawal.requested — vendedor pediu PIX.
  • withdrawal.status_changed — saque foi aprovado/pago/rejeitado.

Headers enviados

http
POST /seu-endpoint HTTP/1.1
Content-Type: application/json
X-Refera-Event: commission.available
X-Refera-Signature: 8f2a4b1c...

Formato da assinatura: a partir de 2026-08 enviamos hex puro (sem prefixo sha256=). Se você já valida com prefixo, remova-o antes de comparar.

Verificando a assinatura

Cada endpoint tem um secret que você recebeu ao criar o webhook. Assine o corpo RAW e compare em constant-time:

node·verify-signature.ts
import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyReferaSignature(rawBody: string, header: string, secret: string) {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(header.trim().toLowerCase())
  return a.length === b.length && timingSafeEqual(a, b)
}
csharp·VerifyHmac.cs
using System.Security.Cryptography;
using System.Text;

public static bool VerifyReferaSignature(string body, string secret, string signatureHex)
{
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var computed = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(body))).ToLowerInvariant();
    var received = signatureHex.Trim().ToLowerInvariant();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.ASCII.GetBytes(computed), Encoding.ASCII.GetBytes(received));
}

Retry policy

Cada entrega vira uma row em refera.webhook_deliveries com status pending. Tentamos entregar in-line uma vez com timeout de 30 segundos; se falhar, um cron Postgres (pg_cron) roda a cada minuto e re-tenta usando pg_net.http_post.

Backoff exponencial

  • Tentativa 1 → 30s
  • Tentativa 2 → 2min
  • Tentativa 3 → 10min
  • Tentativa 4 → 1h
  • Tentativa 5 → 3h
  • Tentativa 6 → 6h
  • Tentativa 7 → 12h
  • Tentativa 8 → 24h → marca dead

Consideramos entrega bem-sucedida qualquer resposta HTTP 2xx. Qualquer outra (incluindo timeout de 30s) conta como falha e agenda retry.

Idempotência

Cada evento tem um event_id único (UUID). Como a mesma entrega pode chegar até 8 vezes se seu endpoint estiver flapping, guarde osevent_id processados pra descartar duplicatas. Veja o case do EscolaNutri (/docs/case-studies/escolanutri) pra exemplo em SQL Server.

Payload exemplo

json
{
  "id": "b6c1a5d8-4f88-4d20-9d3c-77edf1e8b6a1",
  "event": "commission.available",
  "tenant_id": "5e7bde52-...",
  "created_at": "2026-08-10T14:38:20Z",
  "data": {
    "commission_id": "d1b78e05-...",
    "partner_id": "278ddb3b-...",
    "source_sale_id": "a8f2c341-...",
    "amount_brl": 14.99,
    "percentage": 10
  }
}