# Webhooks

Receba um POST assinado no seu servidor sempre que uma ordem ou um reembolso mudar. Assim você não precisa consultar a API a cada poucos segundos.

- [Gerenciar webhooks no painel](https://bytemax.exchange/pt-BR/webhooks)
- [Baixar OpenAPI 3.1](https://bytemax.exchange/api-v1.openapi.json)
- [Página da documentação](https://bytemax.exchange/pt-BR/docs/webhooks)

## Como funciona

1. **Cadastre o webhook**
   Cadastre no [painel de webhooks](https://bytemax.exchange/pt-BR/webhooks) ou por `POST /webhooks`, com a URL do seu servidor e os eventos que quer receber. Sua conta precisa ter uma chave de API ativa. A URL precisa começar com `https://` e usar um domínio público que já exista no DNS: endereços IP e URLs com usuário ou senha são recusados com `INVALID_INPUT`.

2. **Guarde o segredo de assinatura**
   Ele vem na resposta da criação, no formato `whsec_…`, e pode ser consultado de novo com `GET /webhooks/{id}?include_signing_secret=true`. Não há como gerar outro: se vazar, exclua o webhook e crie um novo. Não confunda com a chave de API.

3. **Receba o POST**
   Cada evento chega num POST, com o JSON do evento no corpo e os cabeçalhos `svix-id`, `svix-timestamp` e `svix-signature`.

4. **Valide, grave e responda 2xx**
   Confira a assinatura, grave o evento e só então responda. Qualquer resposta fora de 2xx, ou a falta de resposta, gera novas tentativas.

## Eventos

| Evento | Quando é enviado |
| --- | --- |
| `order.created` | Ordem criada. A consulta da ordem traz os valores definitivos e o pagamento. |
| `order.completed` | A ordem foi paga e concluída. Confirme com a consulta (`status` `completed`) antes de liberar o pedido. |
| `order.expired` | A ordem expirou sem pagamento. |
| `order.canceled` | A ByteMax cancelou a ordem, por exemplo porque o pagamento não pôde ser aceito e foi devolvido ao pagador. |
| `order.updated` | Mudou um dado da ordem sem mudar o estado: o valor reembolsado, o valor retido ou os dados de pagamento. Não indica pagamento: a ordem paga envia `order.completed`. |
| `refund.created` | Um reembolso pedido pela sua conta, pela API ou pelo painel, foi registrado. Reembolsos feitos pelo suporte aparecem só em `order.updated`. |
| `refund.succeeded` | O reembolso terminou: o valor foi devolvido ao pagador. |
| `refund.failed` | Não foi possível concluir o reembolso. |
| `webhook.test` | Evento de teste, enviado só para o webhook que você testou. |

Cada evento traz `type`, que diz o que aconteceu, e `related_object`: o `type` do objeto (`order`, `refund` ou `webhook`), o `id` e a `url`, caminho da consulta em `https://bytemax.exchange`. Para agir, consulte essa `url`: a consulta sempre devolve o estado atual, mesmo que o evento chegue atrasado, repetido ou fora de ordem.

**Exemplo: order.completed**

```json
{
  "id": "evt_example",
  "type": "order.completed",
  "created_at": "2026-09-10T13:05:00.000Z",
  "related_object": {
    "type": "order",
    "id": "ord_example",
    "url": "/api/v1/orders/ord_example"
  }
}
```

### Estados de publicação

`GET /events` e `GET /events/{id}` trazem `publication_status`, que diz se o evento saiu da ByteMax. O resultado no seu servidor fica nas entregas.

- `pending`: Registrado e aguardando o envio, ou em nova tentativa. A ByteMax tenta publicar por cerca de 2 horas; depois disso, o evento fica `not_published`.
- `published`: Enviado para entrega. O resultado de cada tentativa fica nas entregas.
- `not_published`: Não enviado: sua conta estava sem chave de API ativa, nenhum dos seus webhooks ativos assinava esse tipo de evento naquele momento, ou a publicação falhou. Não pode ser reenviado; consulte a ordem ou o reembolso para ver o estado atual.

## Receber e verificar

Valide com o segredo e o corpo bruto da requisição, antes de interpretar o JSON. Use a biblioteca oficial do Svix na sua linguagem: ela confere a assinatura e a tolerância de tempo do `svix-timestamp`. Não implemente a verificação à mão.

[Como verificar assinaturas com a biblioteca oficial (docs.svix.com)](https://docs.svix.com/receiving/verifying-payloads/how)

**O que chega no seu servidor**

```http
POST /webhooks/bytemax HTTP/1.1
Content-Type: application/json
svix-id: msg_2gkQ7Wc9cD6yYq1xkKz7GJb3wQm
svix-timestamp: 1789045500
svix-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhUjM8G3xRk2w1vNq4T0=

{ "id": "evt_example", "type": "order.completed", ... }
```

## Processar sem duplicar

Eventos podem chegar mais de uma vez e fora de ordem. Cinco regras evitam efeitos duplicados:

- Use o `id` do evento como chave única: grave antes de responder e ignore repetidos.
- Não dependa da ordem de chegada: a cada evento, consulte a ordem ou o reembolso e aja pelo estado que a consulta devolver.
- Grave o efeito do evento e a marca de processado na mesma transação.
- Responda 2xx só depois de gravar. Se a gravação falhar, responda 5xx para receber de novo.
- Trabalho demorado fica fora da resposta: enfileire e responda rápido.

## Testar e reenviar

`POST /webhooks/{id}/test-events` com `Content-Type: application/json` e `{}` no corpo envia um `webhook.test` para esse webhook, que precisa estar ativo. Acompanhe a publicação em `GET /events/{id}` e confira no seu servidor se o evento chegou e a assinatura foi aceita. O teste comprova a entrega e a assinatura; para testar o tratamento de ordens e reembolsos, acompanhe uma ordem real.

Entregas que falham recebem até 7 novas tentativas automáticas: depois de 5 segundos, 5 minutos, 30 minutos, 2 horas, 5 horas, 10 horas e 10 horas. Um webhook que falha sem parar por 5 dias é desativado automaticamente. O histórico de cada tentativa fica no painel e em `GET /webhooks/{id}/deliveries`. Para reenviar eventos específicos, use `POST /webhooks/{id}/redeliveries` com até 100 IDs. A chamada responde 200 mesmo quando alguns eventos são recusados: leia o `status` de cada item.

- `queued`: O reenvio foi aceito.
- `rejected`: Nada foi reenviado. O `code` diz o motivo: `EVENT_NOT_FOUND` (o ID não existe), `EVENT_NOT_ACCEPTED` (o webhook não assina esse tipo de evento, ou o teste é de outro webhook), `EVENT_NOT_PUBLISHED` (o evento ainda está `pending` ou ficou `not_published`) ou `REDELIVERY_REJECTED` (o serviço de entregas recusou). Um evento `pending` pode ser reenviado quando `GET /events/{id}` mostrar `published`.
- `retry`: O reenvio não foi feito ou não pôde ser confirmado: o tempo da chamada acabou, o serviço de entregas estava ocupado ou falhou. Envie de novo; se o evento chegar duas vezes, o `id` dele evita efeito duplicado.

## Limites

- Até 200 webhooks por conta, um por URL.
- Eventos ficam consultáveis por 30 dias depois da publicação.
- Listas usam `items` e `next_cursor`: 30 por página, no máximo 100. Um cursor só vale na mesma lista e com os mesmos filtros.

## Erros

Autenticação, erros gerais e limite de chamadas estão na [página da API](https://bytemax.exchange/pt-BR/docs/api). Estes códigos são próprios dos webhooks:

- `WEBHOOK_URL_EXISTS` (HTTP 409): Você já tem um webhook com esta URL.
- `WEBHOOK_LIMIT_REACHED` (HTTP 409): Você já tem 200 webhooks. Exclua um antes de criar outro.
- `WEBHOOK_NOT_ENABLED_FOR_V1` (HTTP 409): Testes e reenvios exigem um webhook ativo e na v1. Ative o webhook ou, se ele veio da API anterior, migre-o para a v1 com `PATCH`.
- `WEBHOOK_VERSION_REQUIRED` (HTTP 400): Um webhook da API anterior só pode ser alterado junto com a migração para a v1: envie `api_version: "v1"` e os novos `events`.
- `WEBHOOK_NOT_FOUND` (HTTP 404): Não há webhook com este ID na sua conta.
- `EVENT_NOT_FOUND` (HTTP 404): Não há evento com este ID. Eventos ficam guardados por 30 dias depois da publicação.
- `INVALID_CURSOR` (HTTP 400): O cursor não pertence a esta lista com estes filtros. Recomece sem `cursor`.

Uma resposta 503 `SERVICE_UNAVAILABLE` indica que o serviço de entregas está temporariamente indisponível. Repita depois de alguns segundos.

## Referência

Dez operações, todas com `Authorization: Bearer <API_KEY>`. As três mais usadas aparecem completas; as demais seguem o mesmo formato.

### Criar webhook

`POST /webhooks`

Cria o webhook e devolve o segredo de assinatura.

**Corpo JSON**

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| `url` | `string` | Sim | Endereço `https://` do seu servidor que recebe os eventos, num domínio público. |
| `events` | `array` | Sim | Tipos de evento que o webhook recebe, sem repetir: um ou mais dos oito de ordem e reembolso em Eventos. `webhook.test` não pode ser assinado: ele só chega pelo teste. |
| `enabled` | `boolean` | Não | Se o webhook recebe eventos. Padrão: `true`. |

**Requisição**

```bash
curl --request POST "https://bytemax.exchange/api/v1/webhooks" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{"url":"https://merchant.example/webhooks/bytemax","events":["order.created","order.completed","order.expired","order.canceled","order.updated","refund.created","refund.succeeded","refund.failed"],"enabled":true}'
```

**Resposta 201**

```json
{
  "id": "ep_example",
  "url": "https://merchant.example/webhooks/bytemax",
  "events": [
    "order.created",
    "order.completed",
    "order.expired",
    "order.canceled",
    "order.updated",
    "refund.created",
    "refund.succeeded",
    "refund.failed"
  ],
  "enabled": true,
  "api_version": "v1",
  "signing_secret": "<WEBHOOK_SIGNING_SECRET>"
}
```

### Enviar teste

`POST /webhooks/{id}/test-events`

Envia um `webhook.test` para este webhook. O corpo é um objeto JSON vazio.

**Parâmetros**

| Parâmetro | Onde | Obrigatório |
| --- | --- | --- |
| `id` | caminho | Sim |

**Requisição**

```bash
curl --request POST "https://bytemax.exchange/api/v1/webhooks/ep_example/test-events" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{}'
```

**Resposta 202**

```json
{
  "id": "evt_test_example",
  "type": "webhook.test",
  "created_at": "2026-09-10T13:05:00.000Z",
  "related_object": {
    "type": "webhook",
    "id": "ep_example",
    "url": "/api/v1/webhooks/ep_example"
  },
  "publication_status": "pending"
}
```

### Reenviar eventos

`POST /webhooks/{id}/redeliveries`

Reenvia até 100 eventos para este webhook. A resposta traz o resultado de cada um.

**Parâmetros**

| Parâmetro | Onde | Obrigatório |
| --- | --- | --- |
| `id` | caminho | Sim |

**Corpo JSON**

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| `event_ids` | `array` | Sim | IDs dos eventos a reenviar, de 1 a 100, sem repetir. |

**Requisição**

```bash
curl --request POST "https://bytemax.exchange/api/v1/webhooks/ep_example/redeliveries" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{"event_ids":["evt_example"]}'
```

**Resposta 200**

```json
{
  "items": [
    {
      "event_id": "evt_example",
      "status": "queued"
    }
  ]
}
```

### Outras operações

| Operação | Nome | O que faz |
| --- | --- | --- |
| `GET /webhooks` | Listar webhooks | Todos os webhooks da sua conta, inclusive os criados na API anterior (`api_version: "legacy"`). |
| `GET /webhooks/{id}` | Consultar webhook | Um webhook. Com `include_signing_secret=true`, inclui o segredo de assinatura. |
| `PATCH /webhooks/{id}` | Alterar webhook | URL, eventos ou ativação. Para migrar para a v1 um webhook criado na API anterior, envie `api_version: "v1"` junto com os novos `events`. |
| `DELETE /webhooks/{id}` | Excluir webhook | Exclui o webhook. |
| `GET /events` | Listar eventos | Eventos dos últimos 30 dias e seu estado de publicação. |
| `GET /events/{id}` | Consultar evento | Um evento e seu estado de publicação. |
| `GET /webhooks/{id}/deliveries` | Listar entregas | Tentativas de entrega de um webhook: `succeeded` quando o seu servidor respondeu 2xx e `failed` quando não respondeu ou respondeu outro status. Filtros por estado e tipo. |
