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 ​