Idempotência e reentrega
A IORQ garante entrega no mínimo uma vez (at-least-once) — não exatamente uma vez. Você pode receber o mesmo evento duas ou mais vezes. Esta página explica por que isso acontece e como construir seu consumidor para ser correto sob duplicação.
1. Por que ocorre duplicação
Três cenários produzem duplicação legítima:
- Timeout do seu lado: a IORQ envia o evento, você processa, mas sua resposta demora demais. A IORQ assume falha e reenvia. Seu sistema processou duas vezes.
- Erro
5xxcom efeito colateral: seu endpoint persistiu o evento mas explodiu antes de responder. A IORQ reenvia, sua persistência grava de novo. - Reenvio manual (replay): você pediu a reentrega via
POST /events/{event_id}/replaypara reconciliar uma divergência.
2. Como deduplicar
As entregas trazem X-IORQ-Signature/X-IORQ-Timestamp (autenticidade — ver Configuração e segurança), mas não um event_id dedicado. Deduplique pelo conteúdo do data — uma chave estável derivada de entity_id + status (+ nosso_numero/installment_code quando aplicável):
def handle_webhook(payload):
data = payload["data"]
key = f"{data['entity_id']}:{data['status']}:{data.get('nosso_numero','')}"
if processed_events.exists(key):
return 200 # já processado, ack e ignora
with database.transaction():
process_event(payload)
processed_events.insert(key)
return 200Pontos de atenção:
- Tabela de chaves processadas com índice único — violar a constraint = duplicação detectada.
- Mesmo transaction-scope entre processar o evento e inserir a chave — sem isso, dá race.
3. Política de retentativa da IORQ
Quando a IORQ não recebe 2xx, ela reenvia com backoff (Fibonacci), até 7 tentativas por webhook. O atraso entre tentativas cresce e é limitado a 15 minutos (900 s):
| Tentativa | Atraso aproximado |
|---|---|
| 1 | Imediato (entrega original) |
| 2 | ~20 s |
| 3 | ~30 s |
| 4 | ~50 s |
| 5 | ~80 s |
| 6 | ~130 s |
| 7 | ~210 s |
Esgotadas as tentativas, a entrega fica registrada como falha e disponível para replay manual. Consulte o histórico via GET /attempts/event/{event_id}.
Não dependa apenas dos webhooksSe seu endpoint fica fora além da janela de tentativas, eventos podem ficar como falha. Implemente reconciliação periódica via
GET /api/loan/?originator_proposal_code={código}como salva-vidas.
4. Garantia de ordem
Não assuma ordem global de entrega — eventos de operações diferentes podem chegar em qualquer ordem, e a mesma operação pode ter reentregas fora de ordem. Reconcilie sempre pelo estado atual da operação (via consulta) em vez de confiar na sequência de webhooks.
5. Reentregas manuais
Você pode forçar a reentrega (replay) de um evento específico:
curl -X POST 'https://webhook-sender-manager-production.iorq.com.br/events/{event_id}/replay' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Fund-Id: 374485cf-f6df-467c-b91d-9f14082c6f36'Ou listar as tentativas de entrega de um evento:
curl 'https://webhook-sender-manager-production.iorq.com.br/attempts/event/{event_id}' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Fund-Id: 374485cf-f6df-467c-b91d-9f14082c6f36'6. Idempotência em requests da API
Para chamadas de você para a IORQ, a chave de negócio é o originator_proposal_code. Não há header Idempotency-Key nem campo idempotency_key:
| Endpoint | Chave / dedup |
|---|---|
POST /api/loan/ | originator_proposal_code (reenvio no mesmo minuto retorna a operação existente, sem duplicar) |
POST /api/installment/settle | Dedup interno por operação/parcela |
POST /api/installment/prepayment | Dedup interno por operação/parcela |
POST /api/loan/repurchase | originator_proposal_code |
PATCH /api/loan/renegotiate | renegotiated_loans[].originator_proposal_code + new_loan.originator_proposal_code |
