Appearance
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/webhooksCabeçalhos da requisição
| Header | Required | Description |
|---|---|---|
X-API-KEY | sim | Chave de API do painel |
X-Real-IP | sim | Um 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
| Method | Path | Action |
|---|---|---|
GET | /apiv2/reports/webhooks | listar 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"
}| Field | Description |
|---|---|
event | report.ready — chave de roteamento para o seu manipulador |
delivery_id | Chave de desduplicação. Também enviada como o cabeçalho X-Netts-Delivery |
order_id | O número do pedido fornecido a você quando você colocou o relatório na fila |
order_type | statement ou balance_at_date |
download_url | Caminho para baixar o arquivo, relativo a https://netts.io |
artifact.sha256 | Checksum, para que você possa verificar o que baixou |
confirmed_at | UTC |
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.
- Desduplique por
delivery_id. Uma repetição deve ser uma operação sem efeito (no-op) do seu lado. - Verifique a assinatura antes de agir, e não depois.
- Responda
2xxsomente 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.
| HTTP | Meaning |
|---|---|
401 | chave ausente ou inválida, ou o IP de origem não está na lista de permissões |
404 | nenhum endpoint deste tipo na sua conta |
409 | a função solicitada já está em uso — role primary is already taken |
422 | a URL foi rejeitada, ou o corpo do PATCH não trouxe nada para alterar |
429 | limite 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
- Arquivos de extrato — solicitando o relatório que aciona esta notificação
- Webhooks de pedidos — o registro separado para eventos de Energy, Bandwidth e ativação