Retentativa e idempotência
Se a entrega ao seu endpoint falhar por um erro transiente (timeout, erro de rede, HTTP 5xx, 429 ou 408), a INFI reentrega automaticamente com backoff crescente — até 6 tentativas ao longo de ~9 horas:
1min → 5min → 30min → 2h → 6h
Você não perde mais um evento por uma instabilidade pontual do seu lado.
O que é retentado (e o que não é)
| Resposta do seu endpoint | Comportamento |
|---|---|
2xx | Sucesso — entrega encerrada. |
| Timeout (8s) ou erro de rede | Transiente → retry com backoff. |
5xx, 429, 408 | Transiente → retry com backoff. |
4xx (400, 401, 403, 422…) | Permanente — seu endpoint recusou o evento. Não é retentado (martelar não resolveria). Corrija a causa (auth, validação de assinatura, rota) e use o reenvio manual. |
| Esgotou as 6 tentativas | Marcada como esgotada. Após corrigir o endpoint, use o reenvio manual. |
Cada reentrega usa um timestamp novo — e portanto uma assinatura nova (para passar em proteções de replay). O eventId permanece o mesmo. Sempre revalide o HMAC usando o X-Infi-Timestamp recebido naquela requisição; nunca compare a assinatura com uma anterior. Veja Assinatura.
Estratégia recomendada: event-driven
Com o retry automático, o webhook é confiável o bastante para ser o caminho primário. Você não precisa de polling agressivo.
- Reaja ao webhook assim que ele chega — confirme o depósito e atualize seu sistema. O evento
transaction.paidjá traz tudo que você precisa:transactionId,status,amountCents,netCents,paidAt,endToEndId,payer. - Mantenha seu saldo localmente somando
netCentsde cadatransaction.paid(e subtraindo os saques), em vez de consultarGET /v1/balanceem loop. - Reconcile leve como rede de segurança: uma varredura
GET /v1/transactionsa cada 1–2 minutos (não a cada poucos segundos) cobre qualquer evento que escape — por exemplo, se o seu endpoint ficou fora além da janela de retry.
Consultar GET /v1/balance ou GET /v1/transactions/:id a cada poucos segundos é desnecessário com o webhook + retry, e consome rate limit à toa. Prefira reagir ao webhook e reconciliar esparsamente.
Idempotência no seu lado
A INFI garante um campo eventId único por evento (ex.: evt_1715000000000_abcdef12), entregue:
- No header
X-Infi-Event-Id. - No campo
eventIddo corpo.
O mesmo eventId chega quando:
- A entrega é reentregue automaticamente (retry após falha transiente).
- Múltiplas URLs do mesmo merchant matcham o evento (fanout).
- A entrega é reenviada manualmente pelo painel.
Por isso, deduplicar por eventId é obrigatório — armazene os processados e ignore repetições:
async function handleWebhook(req) {
const eventId = req.headers['x-infi-event-id'] || req.body.eventId
if (await db.processedEvents.has(eventId)) return // já processado
await db.processedEvents.add(eventId, ttl: '7d')
// aplica a transição
await applyTransition(req.body.transactionId, req.body.status)
}Como webhook e reconcile (polling) podem confirmar a mesma transição, trate o processamento de forma idempotente também por transactionId + status:
async function applyTransition(transactionId, newStatus) {
const current = await db.orders.getByTx(transactionId)
if (current?.status === newStatus) return // já aplicado
await db.orders.setStatus(transactionId, newStatus)
await onStatusChanged(transactionId, newStatus)
}Cuidados
- Timeout curto: 8s por tentativa. Não faça trabalho pesado no handler — responda
200rápido e processe em background/fila. Um handler lento vira timeout → retry desnecessário. - Responda o status certo:
2xx= recebido (encerra).5xx/timeout = “tente de novo” (vira retry).4xx= “não me mande isso” (encerra como permanente, sem retry). Use isso a seu favor. - Histórico no painel: toda entrega — inclusive as que falharam e cada retry automático — fica em Webhooks → Histórico de entregas por 60 dias, com payload exato e timeline de tentativas. Útil para debug pós-mortem.
- Reenvio manual: se uma entrega esgotou as tentativas ou foi recusada (4xx), corrija o endpoint e dispare um reenvio manual — as tentativas manuais aparecem com
manual: trueno histórico.