Skip to content
Translated page. The English version is the source of truth.

POST /apiv2/reports/webhooks

Cadastre uma URL e o NETTS fará uma chamada para ela quando um relatório estiver pronto, em vez de você precisar consultar o status periodicamente.

Esses endpoints são separados dos webhooks de pedidos. Cadastrar-se lá não inscreve você para notificações de relatórios, e vice-versa. O formato de transmissão — assinatura, cabeçalhos, comportamento de novas tentativas — é idêntico, portanto, um manipulador escrito para um funciona para o outro.

URL base do endpoint

https://netts.io/apiv2/reports/webhooks

Cabeçalhos da requisição

HeaderRequiredDescription
X-API-KEYsimChave de API do painel
X-Real-IPsimUm endereço da lista de permissões da chave

Primário e backup

Até dois endpoints por conta. primary recebe tudo. backup é usado apenas depois que o envio para o primário esgotar as tentativas — e ele é assinado com seu próprio segredo, não o do primário.

Cadastrar

bash
curl -s -X POST 'https://netts.io/apiv2/reports/webhooks' \
  -H 'X-API-KEY: your-api-key' \
  -H 'X-Real-IP: 203.0.113.10' \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://example.com/netts/reports", "role": "primary"}'
json
{
  "status": "success",
  "code": 10000,
  "data": {
    "id": 1,
    "url": "https://example.com/netts/reports",
    "role": "primary",
    "is_active": true,
    "created_at": "2026-09-06 17:05:12+00:00",
    "updated_at": "2026-09-06 17:05:12+00:00",
    "secret": "whsec_<64 hex characters>"
  }
}

O segredo é exibido apenas uma vez, aqui. Ele nunca é retornado novamente — nem pela lista, nem pelo endpoint de leitura. Armazene-o quando recebê-lo. Se for perdido, emita um novo:

bash
curl -s -X POST 'https://netts.io/apiv2/reports/webhooks/1/rotate-secret' \
  -H 'X-API-KEY: your-api-key' -H 'X-Real-IP: 203.0.113.10'

A rotação entra em vigor imediatamente e o segredo antigo deixa de verificar, portanto, implemente o novo valor primeiro se você não puder tolerar uma interrupção.

Gerenciar

MethodPathAction
GET/apiv2/reports/webhookslistar os seus, sem segredos
GET/apiv2/reports/webhooks/{id}ler um
PATCH/apiv2/reports/webhooks/{id}alterar url, ou pausar com is_active: false
DELETE/apiv2/reports/webhooks/{id}removê-lo

A URL deve ser HTTPS pública. Endereços de loopback, privados e link-local são rejeitados, assim como credenciais dentro da URL. Qualquer coisa rejeitada retorna como 422 com o motivo. A verificação é executada novamente imediatamente antes de cada envio, portanto, um endpoint que posteriormente resolva para um endereço privado para de receber.

O que enviamos

json
{
  "event": "report.ready",
  "delivery_id": 4,
  "order_id": "REPxxxxxxxxxxxx",
  "order_type": "statement",
  "client_request_id": "stmt-2026-09-usdt",
  "status": "done",
  "format": "csv",
  "download_url": "/apiv2/reports/REPxxxxxxxxxxxx/download",
  "expires_at": "2026-10-06 15:48:04+00:00",
  "artifact": { "sha256": "…", "size_bytes": 696 },
  "confirmed_at": "2026-09-06T15:48:04Z"
}
FieldDescription
eventreport.ready — chave de roteamento para o seu manipulador
delivery_idChave de desduplicação. Também enviada como o cabeçalho X-Netts-Delivery
order_idO número do pedido fornecido a você quando você colocou o relatório na fila
order_typestatement ou balance_at_date
download_urlCaminho para baixar o arquivo, relativo a https://netts.io
artifact.sha256Checksum, para que você possa verificar o que baixou
confirmed_atUTC

Todos os registros de data e hora estão em UTC.

Verificando a assinatura

X-Netts-Signature: sha256=<hex>
X-Netts-Timestamp: <unix seconds>
X-Netts-Delivery:  <delivery_id>

A assinatura é HMAC-SHA256 sobre "<timestamp>." + raw body, calculada com o segredo do endpoint que recebeu a requisição. Compare em tempo constante e rejeite qualquer coisa cujo registro de data e hora caia fora de uma janela de ±5 minutos.

python
import hmac, hashlib, time

def verify(raw_body: bytes, sig_header: str, ts_header: str, secret: str) -> bool:
    if abs(time.time() - int(ts_header)) > 300:      # anti-replay
        return False
    signed = f"{ts_header}.".encode() + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig_header)

Assine com o segredo da URL na qual a requisição chegou: primário e backup possuem segredos diferentes.

O envio é at-least-once (pelo menos uma vez)

Uma resposta perdida causa uma nova tentativa, portanto, o mesmo evento pode chegar duas vezes.

  1. Desduplique por delivery_id. Uma repetição deve ser uma operação sem efeito (no-op) do seu lado.
  2. Verifique a assinatura antes de agir, e não depois.
  3. Responda 2xx somente após ter armazenado o evento. Qualquer outra coisa, ou um tempo limite esgotado, é tratado como falha e tentado novamente.

As novas tentativas para um endpoint ocorrem em 1 minuto, 5 minutos, 15 minutos, 1 hora, 6 horas e 24 horas — seis tentativas ao todo, estendendo-se por um pouco mais de 31 horas. Quando elas forem esgotadas e você tiver cadastrado um backup, o envio passa para ele e o cronograma recomeça com o próprio segredo do backup. O delivery_id permanece o mesmo durante todo o processo, portanto, um evento que falhou no primário e teve sucesso no backup ainda é um único evento.

Redirecionamentos não são seguidos.

Limites de taxa

10 requisições por segundo por endpoint, compartilhadas entre todos os clientes.

Respostas de erro

Cadastrar responde 201, deletar responde 204 sem corpo, todo o restante 200.

HTTPMeaning
401chave ausente ou inválida, ou o IP de origem não está na lista de permissões
404nenhum endpoint deste tipo na sua conta
409a função solicitada já está em uso — role primary is already taken
422a URL foi rejeitada, ou o corpo do PATCH não trouxe nada para alterar
429limite de taxa excedido

Uma URL rejeitada retorna como 422 com o motivo especificado, para que você possa mostrá-lo a quem a digitou:

json
{"status": "error", "code": -4, "msg": "url: Value error, invalid webhook_url: resolved address 127.0.0.1 is not public"}

As redações são only https:// URLs are allowed, credentials in URL are not allowed e resolved address <ip> is not public. O último é resolvido no momento do cadastro e novamente imediatamente antes de cada envio, portanto, um nome de host que posteriormente aponte para um endereço privado para de receber.

Relacionados