PortuguêsWebhooksRetentativa e idempotência

Retentativa e idempotência

Retry automático com backoff

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 endpointComportamento
2xxSucesso — entrega encerrada.
Timeout (8s) ou erro de redeTransiente → retry com backoff.
5xx, 429, 408Transiente → 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 tentativasMarcada como esgotada. Após corrigir o endpoint, use o reenvio manual.
A assinatura muda a cada tentativa

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.

  1. Reaja ao webhook assim que ele chega — confirme o depósito e atualize seu sistema. O evento transaction.paid já traz tudo que você precisa: transactionId, status, amountCents, netCents, paidAt, endToEndId, payer.
  2. Mantenha seu saldo localmente somando netCents de cada transaction.paid (e subtraindo os saques), em vez de consultar GET /v1/balance em loop.
  3. Reconcile leve como rede de segurança: uma varredura GET /v1/transactions a 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.
Evite polling agressivo

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 eventId do 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 200 rá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: true no histórico.