# Eventos e entrega

## Os headers

| header | o que é |
| --- | --- |
| `X-AutoConsulta-Signature` | `t=<epoch>,v1=<hex>` — veja [Verificar a assinatura](/webhooks/verificar-assinatura) |
| `X-AutoConsulta-Event-Id` | identificador do evento — **estável entre tentativas** |
| `X-AutoConsulta-Event-Type` | um dos seis: `dossie.completed`, `dossie.partial`, `dossie.failed`, `client.activated`, `supplier.status_changed`, `supplier.seal_expiring` |

## ⭐ Deduplique pelo `Event-Id`

A entrega é **pelo menos uma vez**. Se o seu endpoint demorar, cair ou responder fora do 2xx, nós
tentamos de novo — e o `X-AutoConsulta-Event-Id` **é o mesmo em todas as tentativas**.

⇒ Guarde os `Event-Id` já processados e ignore os repetidos. É a coisa mais importante desta
página: sem isso, uma retentativa vira trabalho duplicado do seu lado.

## O corpo

Todo evento chega no mesmo envelope: `event`, `created_at` e `data`. **O que muda é o `data`, e a
regra é uma só** — ele carrega exatamente o que a rota de leitura equivalente devolveria à sua
credencial, nunca um campo a mais. Webhook é mudança de modo de entrega, não superfície nova.

### Dossiê

```json
{
  "event": "dossie.completed",
  "consultation_id": "00000000-0000-0000-0000-000000000000",
  "created_at": "2026-08-24T03:49:31Z",
  "data": { }
}
```

`data` é o mesmo conteúdo que `GET /status` devolveria para aquele `consultation_id`. O tamanho
varia com a consulta — tipicamente alguns kilobytes.

### Canal de certificação

```json
{
  "event": "supplier.status_changed",
  "created_at": "2026-09-12T01:57:33.569Z",
  "data": {
    "from": null,
    "to": "invited",
    "client_id": "5c1e8a2f-…",
    "document": "39053344705",
    "person_type": "PF",
    "name": "Jose da Silva",
    "status": "invited",
    "valid_until": null,
    "certified_since": null,
    "verification_url": null,
    "certificate_number": null
  }
}
```

⚠️ **Não há `consultation_id` nos eventos do canal** — eles não são sobre uma consulta. O que os
identifica é o par `client_id` + `document`.

| evento | o que vem em `data` |
| --- | --- |
| `client.activated` | a projeção de `GET /distributor/clients` daquele cliente |
| `supplier.status_changed` | `from`, `to` e a projeção do fornecedor em `GET /distributor/clients/{id}/suppliers` |
| `supplier.seal_expiring` | `days_remaining` e a mesma projeção do fornecedor |

`verification_url` e `certificate_number` vêm preenchidos quando o estado é `certified` ou `expired` — a página
pública do selo e o número do certificado, as mesmas chaves da lista de fornecedores — e `null` nos outros estados.
O que esses estados significam está em [Estados do fornecedor](/canal/estados-do-fornecedor).

`from` é o último estado que **nós dissemos a você** sobre aquele fornecedor — e vem `null` no
primeiro evento do par, inclusive quando você assina o webhook depois de a certificação já ter
avançado. ⇒ **Estado que não mudou não vira evento:** reconvidar quem já está `invited` não gera um
segundo aviso.

:::note
Os valores acima são ilustrativos — não use os identificadores do exemplo.
:::

## O que conta como sucesso

**Só 2xx.** Qualquer outra coisa é falha e entra em nova tentativa.

⚠️ **`3xx` conta como falha.** Não seguimos redirecionamentos: se respondermos a um redirect, nada
chegou ao endereço que consentiu em receber. Responda 2xx no próprio endereço cadastrado.

Tempo limite: **10 segundos**. Responda rápido e processe depois.

## Novas tentativas

| tentativa | intervalo nominal |
| --- | --- |
| 1ª | imediata |
| 2ª | 30 s |
| 3ª | 2 min |
| 4ª | 10 min |
| 5ª | 1 h |
| 6ª | 6 h |

Depois da 6ª, desistimos e a entrega fica registrada como não entregue.

:::caution[Os intervalos são arredondados para cima até o próximo minuto]
A fila é verificada **a cada minuto**. Um intervalo de 30 s, portanto, não é observado como 30 s:
dependendo de onde a falha cai dentro do minuto, o reenvio sai entre ~30 s e ~90 s. Os intervalos
maiores sofrem o mesmo arredondamento, proporcionalmente menor.

Trate a coluna acima como **piso**, não como promessa.
:::

## ⭐ Não dependa só do webhook

Webhook é entrega pela rede, e rede falha: seu servidor pode estar fora do ar na janela de
tentativas, um proxy pode engolir a requisição, um deploy pode derrubar o endpoint por alguns
minutos. Nenhuma integração séria de webhook — nossa ou de qualquer outro serviço — deve ser o
**único** caminho pelo qual um resultado chega ao seu sistema.

**Use os dois, e concilie:**

1. **O webhook é o caminho rápido.** Ao receber, valide a assinatura e registre o `Event-Id`.
2. **A rota de leitura é a rede de segurança**, e ela **não cobra**. Para consultas, `GET /status`
   com o `consultation_id`. Para o canal, `GET /distributor/clients/{id}/suppliers` — que devolve o
   estado ATUAL de cada fornecedor, então uma leitura põe tudo em dia, por mais eventos que você
   tenha perdido.
3. **O `Event-Id` costura os dois.** Se o resultado já veio por webhook, o mesmo `Event-Id` já está
   registrado e você ignora — a reconciliação não cria trabalho duplicado.

Uma varredura periódica dos seus `consultation_id` sem desfecho registrado resolve o caso em que a
entrega não chegou, sem que você precise descobrir por que não chegou.

## Ordem de chegada

Não há garantia de ordem. Se dois eventos importam em sequência para você, use o `created_at` do
corpo — não a ordem em que as requisições chegaram.
