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

# Webhooks

> Receba a confirmação de pagamentos e saques em tempo real

Webhooks avisam o seu sistema assim que algo acontece na ZynPay — sem precisar ficar consultando a API. São criados pelo painel, com a sua sessão logada (não com uma chave de API).

## Criar um endpoint

<Steps>
  <Step title="Acesse Integrações → Webhooks">
    No painel, abra **Integrações** e clique em **Novo webhook**.
  </Step>

  <Step title="Informe a URL">
    Deve ser uma URL **HTTPS pública** (não são aceitos IPs privados/localhost).
  </Step>

  <Step title="Escolha os eventos">
    Selecione um ou mais dos eventos abaixo (até 10).
  </Step>

  <Step title="Guarde o secret">
    O secret (`zwhsec_...`) usado para validar a assinatura é exibido **uma única vez**, na criação.
  </Step>
</Steps>

## Eventos disponíveis

| Evento                | Quando é disparado                                              |
| --------------------- | --------------------------------------------------------------- |
| `pix.charge.paid`     | Uma cobrança Pix foi paga.                                      |
| `withdrawal.paid`     | Um saque foi liquidado (Pix enviado).                           |
| `withdrawal.rejected` | Um saque foi rejeitado e o valor voltou ao saldo disponível.    |
| `infraction.updated`  | Uma infração/disputa relacionada a uma cobrança foi atualizada. |

## Formato do payload

Toda entrega é um `POST` com corpo JSON neste formato:

```json theme={null}
{
  "id": "evt_9f2a...",
  "type": "pix.charge.paid",
  "createdAt": "2026-08-25T13:05:00.000Z",
  "data": {
    "merchantId": "a812...",
    "chargeId": "5c9a...",
    "amountCents": 5000,
    "feeCents": 199,
    "netAmountCents": 4801,
    "paidAt": "2026-08-25T13:05:00.000Z"
  }
}
```

## Validando a assinatura

Cada requisição chega com dois cabeçalhos:

| Cabeçalho            | Conteúdo                                                                               |
| -------------------- | -------------------------------------------------------------------------------------- |
| `x-zinpay-event-id`  | ID único do evento (use para evitar processar o mesmo evento duas vezes).              |
| `x-zinpay-signature` | HMAC-SHA256, em hexadecimal, do corpo bruto da requisição usando o seu webhook secret. |

Verifique a assinatura antes de confiar no payload:

```js theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

function isValidZynPaySignature(rawBody, signatureHeader, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signatureHeader, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
```

<Warning>
  Calcule o HMAC sobre o **corpo bruto** da requisição (antes de fazer `JSON.parse`). Reserializar o JSON pode mudar a ordem/espaçamento dos bytes e invalidar a comparação.
</Warning>

## Responda rápido

Seu endpoint deve responder com um status `2xx` em até poucos segundos. Se a entrega falhar (timeout, erro, redirecionamento), a ZynPay tenta novamente com backoff exponencial; depois de esgotar as tentativas, o evento fica marcado como `dead_letter` e você pode reenviá-lo manualmente pelo painel — veja [Ciclo de vida de uma cobrança](/cobrancas/ciclo-de-vida) para reenviar a notificação de um pagamento específico.

<Tip>
  Trate o mesmo `x-zinpay-event-id` como possivelmente repetido — sua rota deve ser idempotente.
</Tip>
