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

# Headers e Verificação de Webhooks

> Headers HTTP e verificação de assinatura Ed25519 em webhooks

# Headers e Verificação de Webhooks

Toda entrega de webhook é um **HTTP POST** com corpo JSON e três headers relevantes.

## Headers HTTP

| Header                       | Valor                         | Descrição                            |
| ---------------------------- | ----------------------------- | ------------------------------------ |
| `Content-Type`               | `application/json`            | Tipo do corpo                        |
| `x-kiwify-digital-signature` | Base64url (sem padding)       | Assinatura EdDSA-Ed25519             |
| `x-kiwify-timestamp`         | Unix ms (ex: `1705423200000`) | Timestamp usado na mensagem assinada |

Exemplo:

```http theme={null}
POST /webhooks/kiwibank HTTP/1.1
Content-Type: application/json
x-kiwify-digital-signature: gnfHkqEfBhKN3lYmGDF_J9fO3jSQtq8d1-agAZPMn4u3huef0kg5XqbnxSj5SCb_
x-kiwify-timestamp: 1705423200000

{"id":"550e8400-e29b-41d4-a716-446655440000","type":"CASHOUT.PIX.TRANSFERS.COMPLETED",...}
```

## Obter a chave pública

Use `GET /v1/webhooks-keys` para listar chaves públicas. Use a chave com `is_active: true`.

Cache a chave por até 24 horas e atualize periodicamente.

## Processo de verificação

### Passo 1: Validar timestamp

Rejeite entregas com timestamp fora de uma janela de **5 minutos** do horário atual (proteção contra replay).

### Passo 2: Reconstruir a mensagem assinada

Formato PoP (mesmo padrão da autenticação da API, com diferença no `uri`):

```text theme={null}
{url_path}:POST:{raw_body}:{timestamp}
```

| Componente  | Descrição                                                                           |
| ----------- | ----------------------------------------------------------------------------------- |
| `url_path`  | **Somente o path** da URL registrada (ex: `/webhooks/kiwibank`), não a URL completa |
| `POST`      | Método HTTP (sempre POST)                                                           |
| `raw_body`  | Corpo JSON **exatamente como recebido** — não re-serialize                          |
| `timestamp` | Valor do header `x-kiwify-timestamp`                                                |

### Passo 3: Verificar a assinatura

1. Calcule **SHA-256** dos bytes UTF-8 da mensagem
2. Decodifique `x-kiwify-digital-signature` de base64url (sem padding)
3. Verifique com EdDSA-Ed25519 usando a chave pública ativa

<Warning>
  A assinatura de webhook usa **Ed25519 prehashed** (SHA-256 da mensagem antes da verificação). Isso difere ligeiramente de alguns exemplos genéricos de Ed25519 que assinam a mensagem diretamente.
</Warning>

### Exemplo (Python)

```python theme={null}
import base64
import hashlib
import time
from urllib.parse import urlparse
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.hazmat.primitives import serialization

def verify_webhook(endpoint_url: str, body_bytes: bytes, signature_b64: str, timestamp_ms: int, public_key_pem: str) -> bool:
    if abs(int(time.time() * 1000) - timestamp_ms) > 300_000:
        return False

    path = urlparse(endpoint_url).path or "/"
    message = f"{path}:POST:{body_bytes.decode('utf-8')}:{timestamp_ms}"
    digest = hashlib.sha256(message.encode("utf-8")).digest()

    public_key = serialization.load_pem_public_key(public_key_pem.encode())
    padding = (4 - len(signature_b64) % 4) % 4
    signature = base64.urlsafe_b64decode(signature_b64 + "=" * padding)

    try:
        public_key.verify(signature, digest)
        return True
    except Exception:
        return False
```

## Erros comuns

* Usar a URL completa em vez de apenas o path na mensagem assinada
* Re-serializar o JSON (espaços/ordem de chaves alteram a assinatura)
* Verificar Ed25519 diretamente sobre a mensagem sem SHA-256 prévio
