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

# Webhooks: entrega e retentativas

> O que acontece quando seu endpoint falha: retentativas automáticas com backoff, desativação automática e como reconciliar depois.

Esta página explica o que o Z2Pay faz **depois** de enviar um webhook: como uma entrega é
confirmada, o que acontece quando o seu servidor falha, quando um webhook é desativado
automaticamente e como colocar tudo em dia depois de um incidente.

<Info>
  Se você ainda não recebe webhooks, comece pela
  [Visão geral](/pt-BR/webhooks/visao-geral) — lá está o formato do envelope e a validação
  de assinatura. Esta página assume que seu endpoint já está cadastrado.
</Info>

## Confirmação de recebimento

Uma entrega é considerada **confirmada** quando o seu endpoint responde **`2xx` em até 30
segundos**. Qualquer outra coisa — resposta fora da faixa `2xx`, timeout ou falha de conexão —
conta como **tentativa falha** e coloca a entrega no ciclo de retentativas automáticas.

<Warning>
  Processe de forma **assíncrona**: enfileire o evento e responda `200` na hora.
  Se o processamento inline passar de 30 segundos, vira timeout — e o Z2Pay vai
  **retentar algo que você já processou**.
</Warning>

## Retentativas automáticas

Depois de uma falha, o Z2Pay reenvia sozinho, com intervalos crescentes (backoff):

| Tentativa     | Intervalo após a falha anterior | Tempo acumulado |
| ------------- | ------------------------------- | --------------- |
| 1ª (original) | —                               | 0               |
| 2ª            | 5 min                           | 5 min           |
| 3ª            | 10 min                          | 15 min          |
| 4ª            | 15 min                          | 30 min          |
| 5ª            | 1 h                             | \~1h30          |
| 6ª            | 6 h                             | \~7h30          |
| 7ª (última)   | 24 h                            | \~31h30         |

Enquanto está no ciclo, a entrega aparece no
[histórico de entregas](/pt-BR/webhooks/deliveries) com status **`retrying`**, e o
`attemptCount` mostra em que ponto da tabela ela está. Depois da 7ª falha, a entrega
vira **falha definitiva** (`failed`) e fica disponível para
[reenvio manual](#registro-e-reenvio-manual).

<Note>
  **O horário da próxima tentativa não é exposto.** A API devolve o `status` e o `attemptCount`, e
  é por eles que se acompanha — `retrying` significa que ela ainda vai sair sozinha. Se você precisa
  de uma estimativa, some os intervalos da tabela a partir do `lastAttemptAt`.
</Note>

<Note>
  Entregas de teste (`isTest: true`, disparadas pelo botão de teste do painel) **não**
  entram no ciclo: falharam, acabou. Elas existem só para validar seu endpoint.
</Note>

## Ordem de entrega não é garantida

Retentativas fazem eventos chegarem **fora de ordem**: a retentativa de um evento antigo pode
chegar **depois** de um evento mais novo da mesma entidade. Exemplo real: um
`payment.pending` que falhou e está sendo retentado pode chegar depois do `payment.paid`
daquele mesmo pagamento. Se o seu código aplicar os eventos na ordem de chegada, o pagamento
"volta" para pendente.

Proteja-se de uma destas duas formas:

### a) Descarte eventos antigos pelo `occurredAt`

O `occurredAt` do envelope é o instante em que o evento **ocorreu** (não o horário do envio —
retentativas reenviam o valor original). Guarde o `occurredAt` mais recente que você processou
por entidade e ignore qualquer evento anterior a ele:

```js theme={null}
const lastSeen = new Map(); // em produção: banco ou cache

export function handleEvent({ data, occurredAt }) {
  const last = lastSeen.get(data.id);
  if (last && occurredAt <= last) return; // antigo: já processamos estado mais novo

  lastSeen.set(data.id, occurredAt);
  apply(data); // seu processamento
}
```

<Tip>
  Strings ISO 8601 em UTC comparam corretamente como texto — `"2026-08-05T10:00:00Z" <
      "2026-08-05T11:00:00Z"` funciona sem converter para `Date`.
</Tip>

### b) Trate o webhook como aviso, não como verdade

A alternativa mais robusta: use o webhook apenas como **aviso de que algo mudou** e consulte a
API (`GET` do recurso, com o `id` que veio em `data`) para obter o **estado atual** antes de
agir. Assim, chegar fora de ordem deixa de importar — você sempre age sobre o estado vigente.

## Idempotência

O `id` do envelope (prefixo `whd_`) é único por entrega, mas **a mesma entrega pode chegar
mais de uma vez**. O caso clássico: seu endpoint processou o evento, mas demorou mais de 30
segundos para responder — para o Z2Pay isso é timeout, conta como falha, e a retentativa
entrega **o mesmo `id` de novo**.

**Deduplique pelo `id`** antes de processar: guarde os `id` já processados e, se chegar
repetido, responda `2xx` sem fazer nada. O passo a passo está em
[Idempotência no seu receptor](/pt-BR/webhooks/visao-geral#idempot%C3%AAncia-no-seu-receptor).

## Desativação automática

Quando uma entrega esgota as 7 tentativas, o Z2Pay olha o histórico do webhook para decidir
se o problema é pontual ou se o endpoint está fora do ar:

* Se **alguma outra entrega teve sucesso** desde que essa entrega foi criada (\~31h30), o
  endpoint está vivo — só aquela entrega vira falha definitiva. **Falha pontual de um evento
  específico nunca desativa o webhook.**
* Se **nenhuma entrega teve sucesso** na janela inteira, o endpoint é considerado fora do ar
  e o webhook é **desativado automaticamente**. Um e-mail é enviado ao responsável pela
  conta informando a URL afetada, desde quando as entregas falham e o último erro observado.

<Warning>
  Trate o e-mail de desativação como **incidente**: enquanto o webhook está desativado,
  nenhum evento novo é enviado — e quanto mais tempo passar, maior a janela que você vai
  precisar reconciliar depois.
</Warning>

## Reativação e reconciliação

A reativação é **manual**, na página de Webhooks do painel. Duas regras importantes sobre o
período em que o webhook ficou desativado:

* Eventos ocorridos nesse período **não são enviados nem enfileirados** — não existe uma fila
  esperando a reativação.
* Após reativar, esses eventos **não são reenviados automaticamente** em nenhuma hipótese.

Para colocar sua base em dia depois de reativar:

<Steps>
  <Step title="Reenvie as entregas com falha">
    Liste as entregas com `status=failed` no
    [histórico de entregas](/pt-BR/webhooks/deliveries) e reenvie cada uma com
    [`POST /webhooks/deliveries/:id/retry`](/pt-BR/webhooks/deliveries-retry).
    Isso cobre o que **chegou a virar entrega** e falhou.
  </Step>

  <Step title="Consulte a API para o que não virou entrega">
    Eventos ocorridos **enquanto o webhook estava desativado** nunca viraram entrega — para
    esses, consulte a API (`GET` dos recursos, filtrando pelo período) e reconcilie o estado
    na sua base.
  </Step>
</Steps>

## Registro e reenvio manual

Cada entrega registra `status`, `attemptCount`, `returnStatus` (o HTTP que seu servidor
respondeu), `returnData` (corpo da resposta), `errorMessage` e `lastAttemptAt`. Consulte tudo em
[`GET /webhooks/deliveries`](/pt-BR/webhooks/deliveries).

O reenvio manual ([`POST /webhooks/deliveries/:id/retry`](/pt-BR/webhooks/deliveries-retry))
recusa com códigos distintos, para você saber exatamente o que impediu:

| Código                     | HTTP | Significado                                                                    |
| -------------------------- | ---- | ------------------------------------------------------------------------------ |
| `DELIVERY_NOT_FOUND`       | 404  | Entrega não existe nesta conta                                                 |
| `DELIVERY_IS_TEST`         | 400  | Entregas de teste não são reenviadas                                           |
| `DELIVERY_ALREADY_RUNNING` | 409  | Ainda em curso (`pending` ou `retrying`) — aguarde o ciclo automático terminar |
| `MAX_RETRIES_EXCEEDED`     | 400  | Limite de reenvios atingido (7 tentativas automáticas + 5 reenvios manuais)    |
| `WEBHOOK_NOT_FOUND`        | 404  | Webhook do qual a entrega veio não existe mais                                 |
| `WEBHOOK_INACTIVE`         | 400  | Ative o webhook e tente de novo                                                |

## Veja também

<CardGroup cols={2}>
  <Card title="Webhooks · Visão geral" icon="webhook" href="/pt-BR/webhooks/visao-geral">
    Envelope do evento, cabeçalhos e validação da assinatura HMAC.
  </Card>

  <Card title="Catálogo de eventos" icon="bell" href="/pt-BR/webhooks/eventos">
    Todos os códigos de evento que você pode assinar.
  </Card>

  <Card title="Histórico de entregas" icon="list" href="/pt-BR/webhooks/deliveries">
    Liste as entregas, filtre por status e inspecione cada tentativa.
  </Card>

  <Card title="Reprocessar entrega" icon="rotate-cw" href="/pt-BR/webhooks/deliveries-retry">
    Reenvie manualmente uma entrega que falhou.
  </Card>
</CardGroup>
