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 5xx com 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}/replay para 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 200

Pontos 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):

TentativaAtraso aproximado
1Imediato (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 webhooks

Se 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:

EndpointChave / dedup
POST /api/loan/originator_proposal_code (reenvio no mesmo minuto retorna a operação existente, sem duplicar)
POST /api/installment/settleDedup interno por operação/parcela
POST /api/installment/prepaymentDedup interno por operação/parcela
POST /api/loan/repurchaseoriginator_proposal_code
PATCH /api/loan/renegotiaterenegotiated_loans[].originator_proposal_code + new_loan.originator_proposal_code

7. Próximos passos