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

# Visão geral de webhooks

> Eventos disponíveis, configuração e política de retry

## Por que webhooks

Webhooks eliminam a necessidade de polling. Em vez de consultar o status repetidamente, o kycert notifica o seu servidor assim que o run é concluído. O endpoint recebe uma requisição POST com o resultado completo — você processa e responde em milissegundos.

## Configuração

No dashboard kycert, vá em **Integrações & API → Webhooks** e cadastre a URL do seu endpoint.

Alternativamente, passe `webhook_url` diretamente no `POST /runs` para sobrescrever o padrão por requisição.

### Requisitos do endpoint

* HTTPS obrigatório em produção (HTTP aceito apenas em sandbox)
* Responder `2xx` em até 30 segundos
* Idempotente — o mesmo evento pode ser entregue mais de uma vez

## Eventos disponíveis

| Evento          | Quando é disparado                                                  | `status` cobertos                                   |
| --------------- | ------------------------------------------------------------------- | --------------------------------------------------- |
| `run.completed` | Run finalizado com decisão — aprovado, reprovado ou em revisão      | `completed`, `blocked`, `pending_review`, `partial` |
| `run.failed`    | Falha técnica irrecuperável — run não produziu resultado de decisão | `failed`                                            |

<Note>
  Runs reprovados por regra crítica chegam via `run.completed` com `status: "blocked"` e `decision: "rejected"`. O evento `run.failed` indica exclusivamente falhas técnicas — nunca reprovações por regra de negócio.
</Note>

## Estrutura do payload

### `run.completed` — aprovado

```json theme={null}
{
  "id": "evt_01J4ZR...",
  "object": "event",
  "event": "run.completed",
  "created": 1718200818,
  "livemode": true,
  "data": {
    "object": "run",
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "decision": "approved",
    "risk_band": "baixo",
    "subject_type": "pf",
    "template_id": "661e9511-f3ac-52e5-b827-557766551111",
    "external_id": "cust_abc123",
    "metadata": { "channel": "app_mobile" },
    "checks_summary": { "total": 12, "valid": 12, "invalid": 0, "no_data": 0, "error": 0 },
    "created_at": "2026-06-12T14:00:00Z",
    "completed_at": "2026-06-12T14:00:18Z"
  }
}
```

### `run.completed` — reprovado por regra crítica (`blocked`)

```json theme={null}
{
  "id": "evt_9a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d",
  "object": "event",
  "event": "run.completed",
  "created": 1781477806,
  "livemode": true,
  "data": {
    "object": "run",
    "id": "9ad7a680-0232-4da5-a3c5-934d65a87b1a",
    "status": "blocked",
    "decision": "rejected",
    "risk_band": "alto",
    "subject_type": "pf",
    "template_id": "f716ee22-3933-407b-9bf3-d82ab391ef94",
    "external_id": null,
    "metadata": {},
    "checks_summary": {
      "total": 19,
      "valid": 0,
      "invalid": 5,
      "no_data": 0,
      "error": 14
    },
    "created_at": "2026-06-14T22:56:44.091Z",
    "completed_at": "2026-06-14T22:56:44.091Z"
  }
}
```

### `run.failed` — falha técnica irrecuperável

```json theme={null}
{
  "id": "evt_7c3f9e2a1b4d5e6f8a9b0c1d2e3f4a5b",
  "object": "event",
  "event": "run.failed",
  "created": 1781477900,
  "livemode": true,
  "data": {
    "object": "run",
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "failed",
    "decision": null,
    "risk_band": null,
    "subject_type": "pf",
    "template_id": "f716ee22-3933-407b-9bf3-d82ab391ef94",
    "external_id": null,
    "metadata": {},
    "checks_summary": { "total": 0, "valid": 0, "invalid": 0, "no_data": 0, "error": 0 },
    "created_at": "2026-06-14T23:05:00.000Z",
    "completed_at": null
  }
}
```

<Warning>
  `run.failed` indica falha técnica irrecuperável — o run não produziu resultado de decisão. Investigue antes de reenviar. **Nunca** é emitido para reprovações por regra de negócio; essas chegam como `run.completed` com `status: "blocked"`.
</Warning>

## Política de retry

Se o seu endpoint retornar um status fora de `2xx` ou não responder em 30 segundos, o kycert tenta novamente com backoff linear:

| Tentativa     | Delay acumulado |
| ------------- | --------------- |
| 1ª (imediata) | —               |
| 2ª            | +5 min          |
| 3ª            | +10 min         |

Após 3 tentativas sem sucesso (total \~15 min), o evento é marcado como `failed` e nenhuma nova tentativa é realizada. O histórico de tentativas fica disponível no dashboard em **Integrações & API → Webhooks → Entregas**.

<Warning>
  **Retorne sempre `200` — mesmo se o processamento falhar internamente.** O kycert retenta em qualquer status fora de `2xx` (incluindo `4xx` e `5xx`). Se o seu código falhar após receber um evento válido, responda `200` e trate o erro de forma assíncrona — evitando consumir o orçamento de 3 tentativas desnecessariamente.
</Warning>

## Deduplicação

O campo `id` do evento é único. Armazene os IDs processados para ignorar duplicatas em caso de retry:

```typescript theme={null}
// Exemplo com Redis
const wasProcessed = await redis.get(`webhook:${event.id}`)
if (wasProcessed) return // já processado
await redis.setex(`webhook:${event.id}`, 86400, '1')

// processar evento
```

## Headers do webhook

Cada requisição de webhook inclui os seguintes headers:

| Header             | Descrição                                                   |
| ------------------ | ----------------------------------------------------------- |
| `kycert-signature` | Assinatura HMAC-SHA256 no formato `t=<timestamp>,v1=<hash>` |
| `kycert-event-id`  | ID único do evento — use para idempotência no receptor      |

## Verificação de assinatura

Todo webhook inclui o header `kycert-signature`. **Sempre verifique** — qualquer servidor pode fazer POST para o seu endpoint.

Consulte [Segurança](/webhooks/seguranca) para o procedimento completo de verificação com exemplos em 3 linguagens.

## Sandbox

Em sandbox, os webhooks são entregues normalmente. Use um serviço como [webhook.site](https://webhook.site) ou ngrok durante o desenvolvimento.

O campo `livemode: false` identifica eventos de sandbox.

<Warning>
  Serviços como webhook.site possuem limites de requisições no plano gratuito. Se o seu endpoint retornar `429`, o kycert vai retentar — mas esgotará as 3 tentativas e marcará a entrega como `failed`. Em produção, use um endpoint real sem rate limit.
</Warning>
