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
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:
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)
}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
{
"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
}
}