> ## Documentation Index
> Fetch the complete documentation index at: https://userepass.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Retries e dead-letter

> Política de reentrega, dead-letter e replay de webhooks.

Toda entrega de webhook segue uma política de reentrega previsível: a primeira
tentativa sai imediatamente e cada falha agenda a próxima por um cronograma
exponencial fixo. Esgotadas as tentativas, a entrega vai para **dead-letter**,
de onde fica consultável e pode ser reenfileirada por replay manual, sem prazo.

Esta página cobre o ciclo de retry, o que conta como sucesso ou falha, o
dead-letter, o log de entregas e como reenviar. Para o formato do envelope e a
assinatura do header, veja [Visão geral](/docs/webhooks/visao-geral) e
[Assinatura](/docs/webhooks/assinatura).

## O que conta como sucesso

Uma tentativa é considerada **bem-sucedida quando seu endpoint responde com
HTTP 2xx** (200 a 299). Qualquer outra coisa conta como falha e dispara o retry:

* Status HTTP fora da faixa 2xx (3xx, 4xx, 5xx). Redirects **não** são seguidos
  (`redirect: manual`). Um `3xx` é falha.
* Timeout de conexão ou resposta. O timeout por tentativa é de **10 segundos**.
* Erro de rede / conexão recusada.

<Warning>
  Responda **2xx o mais rápido possível** e processe o evento de forma
  assíncrona. Se o seu handler demorar mais de 10s para responder, a tentativa
  é abortada e tratada como falha, mesmo que você acabe processando o evento.
</Warning>

O corpo da resposta é registrado no log de entregas truncado a **2 KB** (apenas
para diagnóstico). Ele não afeta o resultado, que depende só do status.

## Cronograma de retry

A política é exponencial com **7 tentativas no total**. A primeira
sai imediatamente; cada falha agenda a próxima pelo offset abaixo, contado a
partir da falha anterior:

| Tentativa | Atraso até a tentativa |
| --------- | ---------------------- |
| 1         | 0s (imediata)          |
| 2         | +30s                   |
| 3         | +5m                    |
| 4         | +30m                   |
| 5         | +2h                    |
| 6         | +12h                   |
| 7         | +24h                   |

Da criação à 7ª tentativa transcorrem cerca de **38,5 horas**. Falhando a 7ª, a
entrega entra em `dead_letter`.

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: evento novo ou replay
    pending --> attempting: tentativa devida (nextAttemptAt vencido)
    failed --> attempting: retry devido
    attempting --> succeeded: resposta 2xx
    attempting --> failed: falha e tentativas < 7
    attempting --> dead_letter: falha na 7ª tentativa
    attempting --> dead_letter: endpoint desativado/removido no meio
    succeeded --> [*]
    dead_letter --> [*]
    dead_letter --> pending: replay manual (nova entrega)
```

Cada entrega (id `whdl_...`) carrega o estado atual e o contador de tentativas:

<ResponseField name="status" type="string">
  Um de `pending`, `attempting`, `succeeded`, `failed`, `dead_letter`.
</ResponseField>

<ResponseField name="attemptCount" type="integer">
  Número de tentativas já concluídas (0 a 7).
</ResponseField>

<ResponseField name="nextAttemptAt" type="string (ISO-8601)">
  Quando a próxima tentativa está agendada. Mantém o último valor nos estados
  terminais (`succeeded`, `dead_letter`).
</ResponseField>

<ResponseField name="response" type="object">
  `{ statusCode?, body?, error? }` da última tentativa, `body` truncado a 2 KB.
</ResponseField>

<ResponseField name="latencyMs" type="integer">
  Latência da última tentativa, em milissegundos.
</ResponseField>

<Note>
  A entrega é **at-least-once**: reentregas após falha ou replay são esperadas.
  Deduplique pelo `id` do evento (`evt_...`) no envelope: o mesmo evento pode
  chegar mais de uma vez. Veja [Visão geral](/docs/webhooks/visao-geral) e
  [Idempotência](/docs/convencoes/idempotencia).
</Note>

## Auto-desativação por taxa de falha

Para proteger endpoints persistentemente quebrados, a saúde do endpoint é
avaliada sempre que uma entrega vai para dead-letter. Considerando a janela
de **72 horas** de tentativas concluídas (`succeeded` + `failed` + `dead_letter`):

* Se houver **amostra mínima de 20 tentativas** **e** a taxa de falha for
  **≥ 95%**, o endpoint é desativado automaticamente.
* O endpoint passa a `status: disabled` com `disabledReason: failure_rate` e é
  emitido o evento `webhook_endpoint.disabled` (com `windowHours: 72`,
  `total` e `failures` no payload).

Um endpoint `disabled` **não recebe novos eventos** e **recusa replay**.
Reative-o com `PATCH /webhook-endpoints/{id}` definindo `status: active` (isso
limpa o `disabledReason`).

<Tip>
  Amostras abaixo de 20 tentativas nunca desativam o endpoint: 19 dead-letters
  não bastam. Assim, endpoints novos ou de baixo volume não são desativados por
  um pico isolado de falhas.
</Tip>

## Log de entregas

Cada endpoint mantém um log das suas entregas dos **últimos 30 dias**
, com request, response, status e latência. Use o log para
diagnosticar falhas e confirmar entregas.

```bash theme={null}
curl https://api.userepass.com/webhook-endpoints/whep_01J9Z.../deliveries \
  -H "Authorization: Bearer rstr_..."
```

Filtre por `status` para isolar problemas, e pagine por cursor (`limit`,
`starting_after`). A janela de 30 dias é aplicada implicitamente.

```bash theme={null}
curl "https://api.userepass.com/webhook-endpoints/whep_01J9Z.../deliveries?status=failed&limit=50" \
  -H "Authorization: Bearer rstr_..."
```

<ParamField query="status" type="string">
  Filtra por estado: `pending`, `attempting`, `succeeded`, `failed` ou
  `dead_letter`.
</ParamField>

<ParamField query="limit" type="integer">
  Itens por página (1 a 100, default 25). Veja [Paginação](/docs/convencoes/paginacao).
</ParamField>

<ParamField query="starting_after" type="string">
  Cursor de paginação (id `whdl_...` da última entrega da página anterior).
</ParamField>

A resposta segue o padrão de listas (`{ data, hasMore }`), em ordem decrescente
(mais recente primeiro):

```json theme={null}
{
  "data": [
    {
      "id": "whdl_01J9ZQ8K4M2P5R7T9V1X3Y5Z7",
      "endpointId": "whep_01J9ZQ7H3K1N4P6R8T0V2X4Y6",
      "eventId": "evt_01J9ZQ5F2H9K1M3P5R7T9V1X3",
      "eventType": "commission.paid",
      "status": "failed",
      "attemptCount": 3,
      "nextAttemptAt": "2026-06-13T18:30:00.000Z",
      "request": { "url": "https://example.com/webhooks/repass" },
      "response": { "statusCode": 500, "body": "Internal Server Error" },
      "latencyMs": 412,
      "replayOfId": null
    }
  ],
  "hasMore": false
}
```

<Note>
  Entregas `succeeded` e `failed` com mais de 30 dias são purgadas
  automaticamente. **Dead-letters não são purgados**: ficam disponíveis para
  replay sem prazo.
</Note>

## Dead-letter

Uma entrega entra em `dead_letter` quando:

* Esgota as 7 tentativas sem nunca receber um 2xx; **ou**
* O endpoint é desativado ou removido enquanto a entrega estava na fila (a
  tentativa encerra com `response.error: "endpoint_disabled_or_missing"`); **ou**
* É uma [entrega de teste](/docs/webhooks/visao-geral) que falhou (one-shot, não entra
  na fila de retry).

A fila de dead-letter é consultada por um endpoint próprio, **sem a janela de
30 dias**:

```bash theme={null}
curl https://api.userepass.com/webhook-endpoints/whep_01J9Z.../dead-letter \
  -H "Authorization: Bearer rstr_..."
```

Aceita apenas `limit` (1 a 100, default 25) e `starting_after` para paginação por
cursor, sem filtro de `status` (a fila já é só de `dead_letter`) e sem a janela
de 30 dias. Veja [Paginação](/docs/convencoes/paginacao).

## Replay manual

Depois de corrigir o problema no seu endpoint, reenvie uma entrega exaurida com
`POST /deliveries/{deliveryId}/replay`. O replay **cria uma nova entrega**
`pending` (com `replayOfId` apontando para a original) e a submete ao
cronograma de retry completo. A entrega original permanece intacta no log.

```bash theme={null}
curl -X POST https://api.userepass.com/deliveries/whdl_01J9Z.../replay \
  -H "Authorization: Bearer rstr_..." \
  -H "Idempotency-Key: replay-whdl_01J9Z-2026-06-13"
```

Resposta `202 Accepted` com a nova entrega:

```json theme={null}
{
  "id": "whdl_01J9ZR0M5P3R6T8V0X2Y4Z6A8C",
  "endpointId": "whep_01J9ZQ7H3K1N4P6R8T0V2X4Y6",
  "eventId": "evt_01J9ZQ5F2H9K1M3P5R7T9V1X3",
  "eventType": "commission.paid",
  "status": "pending",
  "attemptCount": 0,
  "replayOfId": "whdl_01J9ZQ8K4M2P5R7T9V1X3Y5Z7"
}
```

O `POST` aceita `Idempotency-Key`: reenviar a mesma chave devolve a resposta
armazenada (`Idempotent-Replay: true`), sem criar uma segunda entrega. Veja
[Idempotência](/docs/convencoes/idempotencia).

### Regras e erros do replay

<AccordionGroup>
  <Accordion title="Endpoint precisa estar ativo">
    Replay para um endpoint `disabled` retorna `409 conflict`. Reative o
    endpoint (`PATCH status=active`) antes de reenviar.
  </Accordion>

  <Accordion title="Entrega inexistente ou de outra organização">
    Retorna `404 resource_not_found`. Recursos de outra organização também
    retornam 404 (consultas são escopadas por organização). Veja
    [Erros](/docs/convencoes/erros).
  </Accordion>

  <Accordion title="A original não muda">
    O log é imutável: a entrega original mantém seu estado (`failed` ou
    `dead_letter`); a nova nasce `pending` referenciando-a por `replayOfId`.
  </Accordion>
</AccordionGroup>

## Resumo das regras

* Retry exponencial em 0s, 30s, 5m, 30m, 2h, 12h, 24h (7
  tentativas). Esgotadas, vai para dead-letter (consultável, com replay manual).
  Endpoint com falha ≥ 95% em 72h (amostra mínima de 20) é desativado
  automaticamente.
* Log de entregas (request, response, status, latência)
  disponível por 30 dias; dead-letters retidos sem prazo.

<CardGroup cols={2}>
  <Card title="Visão geral" icon="webhook" href="/docs/webhooks/visao-geral">
    Como funcionam os webhooks, envelope e entrega at-least-once.
  </Card>

  <Card title="Assinatura" icon="signature" href="/docs/webhooks/assinatura">
    Valide o header `Repass-Signature` (HMAC-SHA256) das entregas.
  </Card>

  <Card title="Catálogo de eventos" icon="list" href="/docs/webhooks/catalogo-de-eventos">
    A taxonomia de eventos que você pode assinar por endpoint.
  </Card>

  <Card title="Idempotência" icon="arrows-rotate" href="/docs/convencoes/idempotencia">
    Deduplique entregas reentregues e replays seguros.
  </Card>
</CardGroup>
