Appearance
Webhooks — notificações de pedidos
Registre um endpoint HTTPS para receber um webhook assinado no instante em que um dos seus pedidos for atendido e verificado on-chain. Em vez de fazer polling, você continua seu fluxo (por exemplo, liberar USDT) assim que a notificação chegar.
Três eventos são entregues:
| Evento | Enviado quando |
|---|---|
delegation.confirmed | Um aluguel de energy (1h / 5m) é confirmado on-chain |
bandwidth.delegated | Um pedido de bandwidth é atendido |
activation.confirmed | Uma ativação de endereço é executada on-chain |
Esta página aborda a API de gerenciamento (criar / listar / editar / rotacionar secret / excluir seus endpoints) e o formato dos webhooks que entregamos a você.
ℹ️ Funções. Você gerencia seus endpoints aqui. A entrega é realizada pela Netts de forma assíncrona após o pedido ser verificado — não há necessidade de fazer polling. Apenas eventos de sucesso são enviados; falhas e timeouts nunca são entregues.
🔒 Todo hash que enviamos é verificado on-chain primeiro. Um webhook é disparado somente depois que cada hash de transação nele contido for encontrado em um bloco. Se um hash ainda não estiver em um bloco, a entrega é retida e verificada novamente a cada 30 segundos por até 5 minutos; se nunca for incluído, nada é enviado para esse pedido. Você nunca receberá um hash que não exista on-chain.
URL base do endpoint
https://netts.io/apiv2/webhooksCabeçalhos da requisição
| Cabeçalho | Obrigatório | Descrição |
|---|---|---|
| Content-Type | Sim (para POST/PATCH) | application/json |
| X-API-KEY | Sim | Sua chave de API do painel da Netts |
| X-Real-IP | Sim | Endereço IP da sua whitelist |
Seu user_id é derivado da chave de API — você nunca o envia. Você pode visualizar e modificar apenas os seus próprios endpoints.
Endpoint principal e de backup
Você registra no máximo dois endpoints, e cada um possui uma função (role):
| Função | Finalidade |
|---|---|
primary | O endereço para o qual todo webhook é entregue. |
backup | Fallback. Usado somente quando a entrega para o primary falha após o esgotamento das tentativas. |
Um único pedido confirmado gera um único webhook. Ele não é distribuído em fan-out: o mesmo evento nunca é enviado para ambos os endereços simultaneamente. O endpoint backup existe para resiliência — se o seu host principal estiver inacessível ou continuar retornando status não-2xx, a entrega passa para o backup em vez de ser descartada.
O primeiro endpoint que você criar se torna o primary, o segundo se torna o backup. Você pode informar a role explicitamente ou alterná-las posteriormente com PATCH.
Por que não uma URL separada por tipo de operação? Porque o tipo de evento viaja dentro do corpo, no campo
event. Um único manipulador, uma única verificação de assinatura, e novos tipos de eventos começam a chegar sem que você precise registrar nada novo.
Gerenciar endpoints
Criar — POST /apiv2/webhooks
Registra um novo endpoint e retorna um secret exibido apenas uma vez (armazene-o — ele assina cada webhook recebido).
json
// request body — role is optional
{
"url": "https://your-server.example/netts/delegation-hook",
"role": "primary"
}Se você omitir a role, a primeira disponível será atribuída: primary, depois backup.
json
// response 201
{
"detail": {
"code": 10000,
"status": "created",
"data": {
"id": 1,
"url": "https://your-server.example/netts/delegation-hook",
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"role": "primary",
"is_active": true,
"created_at": "2026-01-01T00:00:00"
}
}
}Requisitos da URL (validados na criação e em cada edição):
- deve ser
https; - deve resolver para um endereço público — loopback, privado (RFC1918), link-local (incluindo
169.254.169.254) e outras faixas não roteáveis são rejeitados; - sem credenciais na URL (
user:pass@…); - tamanho de até 2048 caracteres.
Uma URL rejeitada retorna 400.
Você pode ter dois endpoints — um primary e um backup. Um terceiro retorna 409 (4090). Solicitar uma role que já está ocupada retorna 409 (4091) — alterne as funções com PATCH ou exclua o endpoint existente primeiro.
bash
curl -X POST https://netts.io/apiv2/webhooks \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-H "X-Real-IP: your_whitelisted_ip" \
-d '{"url": "https://your-server.example/netts/delegation-hook", "role": "primary"}'Listar — GET /apiv2/webhooks
Retorna seus endpoints (o secret nunca é retornado aqui).
json
{
"detail": {
"code": 10000,
"status": "ok",
"data": {
"endpoints": [
{
"id": 1,
"url": "https://your-server.example/netts/delegation-hook",
"role": "primary",
"is_active": true,
"created_at": "2026-01-01T00:00:00",
"updated_at": "2026-01-01T00:00:00"
},
{
"id": 2,
"url": "https://backup.example/netts/delegation-hook",
"role": "backup",
"is_active": true,
"created_at": "2026-01-01T00:00:00",
"updated_at": "2026-01-01T00:00:00"
}
],
"count": 2,
"max_endpoints": 2,
"roles": ["primary", "backup"]
}
}
}Obter um — GET /apiv2/webhooks/{id}
Mesma estrutura de um item da lista (sem secret). Um id de terceiros ou inexistente retorna 404.
Editar — PATCH /apiv2/webhooks/{id}
Altera a url, is_active e/ou role. Envie qualquer subconjunto; um corpo vazio retorna 422. Uma url alterada é revalidada (https / SSRF). Um id de terceiros ou inexistente retorna 404.
json
// request body (any subset)
{ "url": "https://your-server.example/netts/new-hook", "is_active": false }json
// response 200
{
"detail": {
"code": 10000,
"status": "updated",
"data": {
"id": 1,
"url": "https://your-server.example/netts/new-hook",
"role": "primary",
"is_active": false,
"created_at": "2026-01-01T00:00:00",
"updated_at": "2026-01-01T00:00:01"
}
}
}Promovendo o backup. Enviar {"role": "primary"} para seu endpoint de backup inverte as duas funções em uma única transação — o endpoint principal antigo se torna o de backup. Você nunca fica sem um endereço principal, e nenhuma chamada separada é necessária para o outro endpoint.
bash
curl -X PATCH https://netts.io/apiv2/webhooks/2 \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-H "X-Real-IP: your_whitelisted_ip" \
-d '{"role": "primary"}'Defina
is_active: falsepara pausar a entrega sem excluir o endpoint;truepara retomar. Pausar o seuprimarynão promove o backup — a entrega continua visando o principal. Alterne as funções caso queira que o backup assuma o envio.
Rotacionar secret — POST /apiv2/webhooks/{id}/rotate-secret
Gera um novo secret e o retorna uma única vez. O novo secret entra em vigor imediatamente para entregas subsequentes — nenhuma ação adicional é necessária. Cada endpoint possui seu próprio secret: rotacionar o secret do principal não altera o do backup.
json
// response 200
{
"detail": {
"code": 10000,
"status": "rotated",
"data": {
"id": 1,
"secret": "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
}
}
}Excluir — DELETE /apiv2/webhooks/{id}
Exclui permanentemente o endpoint e libera sua função. Retorna 204 (sem corpo); um id de terceiros ou inexistente retorna 404.
bash
curl -X DELETE https://netts.io/apiv2/webhooks/1 \
-H "X-API-KEY: your_api_key" -H "X-Real-IP: your_whitelisted_ip"Os webhooks que entregamos
Quando um dos seus pedidos é atendido, a Netts envia um POST para o seu endpoint primary. Todo corpo é application/json (UTF-8); endereços e hashes são sempre valores completos.
Campos comuns a todos os eventos:
| Campo | Tipo | Descrição |
|---|---|---|
event | string | Tipo de evento — chave de roteamento para o seu manipulador |
delivery_id | integer | ID de entrega — chave de desduplicação do seu lado. Também enviado no cabeçalho X-Netts-Delivery. |
order_id | string | ID do seu pedido |
order_type | string | 1h, 5m, bandwidth ou activation |
tx_hashes | string[] | Todos os hashes de transação da operação, cada um verificado on-chain |
confirmed_at | string | UTC ISO-8601 |
delegation.confirmed — aluguel de energy
json
{
"event": "delegation.confirmed",
"delivery_id": 1,
"order_id": "1Hxxxxxxxxxx",
"order_type": "1h",
"receive_address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"energy_amount": 65000,
"tx_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
"delegation_timestamp": 1700000000000,
"confirmed_at": "2026-01-01T00:00:00Z"
}| Campo | Tipo | Descrição |
|---|---|---|
order_type | string | 1h ou 5m |
receive_address | string | Endereço TRON que recebeu a energy |
energy_amount | integer | Quantidade de energy delegada |
tx_hash | string | Campo legado, mantido para compatibilidade: idêntico a tx_hashes[0] |
delegation_timestamp | integer? | Opcional — presente apenas quando confirmado via caminho do Mongo |
Prefira
tx_hashesem novas integrações — um pedido pode, em princípio, ser atendido por mais de uma transação. O campotx_hashcontinuará funcionando.
bandwidth.delegated — pedido de bandwidth
json
{
"event": "bandwidth.delegated",
"delivery_id": 2,
"order_id": "B1Hxxxxxxxxxxxxx",
"order_type": "bandwidth",
"rental_label": "1h",
"rental_seconds": 3600,
"receive_address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"bandwidth_amount": 400,
"fulfillment": "delegated",
"tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
"confirmed_at": "2026-01-01T00:00:00Z"
}| Campo | Tipo | Descrição |
|---|---|---|
rental_label / rental_seconds | string / integer | Duração do aluguel, ex.: 1h / 3600 |
receive_address | string | Endereço TRON que recebeu a bandwidth |
bandwidth_amount | integer | Unidades de bandwidth (líquidas) |
fulfillment | string | Como o pedido foi atendido — veja abaixo |
Valores de fulfillment:
| Valor | Significado | tx_hashes |
|---|---|---|
delegated | Bandwidth delegada a partir da nossa pool | 1+ hashes |
trx_send | Atendido mediante envio de TRX para o endereço em vez de delegar | 1+ hashes |
already_enough | O endereço já possuía bandwidth livre suficiente — nada foi enviado on-chain | vazio |
already_enough é o único caso em que tx_hashes fica vazio: o pedido é concluído com sucesso, mas não há transação porque nenhuma foi necessária.
activation.confirmed — ativação de endereço
json
{
"event": "activation.confirmed",
"delivery_id": 3,
"order_id": "123456",
"order_type": "activation",
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"activation_type": "ACC_CREATE",
"source": "telegram_bot",
"tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
"confirmed_at": "2026-01-01T00:00:00Z"
}| Campo | Tipo | Descrição |
|---|---|---|
order_id | string | ID do pedido de ativação (string numérica) |
address | string | Endereço TRON que foi ativado |
activation_type | string | ACC_CREATE (AccountCreateContract) ou DIRECT (transferência de TRX) |
source | string | Marcador de origem. Uma tag de serviço ou o ID do pedido de energy que exigiu a ativação |
Apenas ativações reais são entregues. Se for verificado que o endereço já estava ativo e nenhuma transação foi realizada, nenhum webhook é enviado.
Um pedido de energy que também exigiu uma ativação gera dois webhooks — um
activation.confirmede umdelegation.confirmed. Eles são eventos distintos comdelivery_ids separados; roteie-os pelo campoevent.
Cabeçalhos que enviamos:
| Cabeçalho | Valor |
|---|---|
X-Netts-Event | Tipo de evento: delegation.confirmed, bandwidth.delegated ou activation.confirmed |
X-Netts-Delivery | delivery_id (desduplicação) |
X-Netts-Timestamp | segundos unix no momento do envio |
X-Netts-Signature | sha256=<hex>, hex = HMAC_SHA256(secret, "<timestamp>." + raw_body) |
User-Agent | netts-webhook/1.0 |
Verificando a assinatura
A assinatura segue o esquema da Stripe (timestamp.body), calculada sobre os bytes brutos que enviamos. Recalcule-a com o seu secret, compare em tempo constante e rejeite se o cabeçalho X-Netts-Timestamp estiver fora de uma janela de ±5 minutos (proteção contra replay).
Assine com o secret do endpoint que recebeu a requisição: o principal e o de backup têm secrets separados. Se ambos os seus endereços forem atendidos pelo mesmo manipulador, selecione o secret com base na URL em que a requisição chegou.
python
import hmac, hashlib, time
def verify(raw_body: bytes, sig_header: str, ts_header: str, secret: str) -> bool:
# freshness (anti-replay)
if abs(time.time() - int(ts_header)) > 300:
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)
# Flask example:
# ok = verify(request.get_data(),
# request.headers["X-Netts-Signature"],
# request.headers["X-Netts-Timestamp"], SECRET)Semântica de entrega (importante — pelo menos uma vez / at-least-once)
A entrega opera no modelo at-least-once: uma resposta perdida pode acionar uma nova tentativa, portanto você pode receber o mesmo evento duas vezes. Como a ação de negócio (liberação de USDT) envolve valores monetários:
- A desduplicação é obrigatória — processe cada evento de forma idempotente pelo
delivery_id(e/ouorder_id); uma repetição não deve ter efeito operacional. - Verifique o HMAC antes de qualquer ação financeira — não confie no corpo da mensagem até que a assinatura corresponda e o
X-Netts-Timestampesteja dentro da janela válida. - Retorne 2xx somente após ter persistido o evento com segurança — caso contrário, tentaremos enviar novamente (corretamente).
Responda com 2xx para confirmar o recebimento; qualquer status não-2xx ou timeout aciona uma nova tentativa.
Ordem das tentativas:
- As novas tentativas vão para o seu endpoint
primary. A janela depende do tipo de pedido: pedidos de5mrepetem por ~1 minuto, todos os outros tipos por ~10 minutos. - Se a janela se esgotar e você tiver registrado um
backup, a entrega se desloca para ele e o cronograma de tentativas recomeça — assinado com o secret próprio do backup. - Somente após o backup também ser esgotado é que a entrega é marcada como encerrada sem sucesso.
O mesmo delivery_id é mantido durante todo o processo, logo uma mensagem que primeiro falhou no principal e depois obteve sucesso no backup continua sendo um único evento para a sua lógica de desduplicação.
Referência de códigos de erro
| Código | Descrição | Status HTTP |
|---|---|---|
10000 | Sucesso (created / ok / updated / rotated) | 200 / 201 |
- | Excluído (sem corpo) | 204 |
4000 | URL de webhook inválida / insegura (não é https, privada/loopback, credenciais, longa demais) | 400 |
-1 | Chave de API inválida / IP fora da whitelist | 401 |
-1 | Endpoint não encontrado (ou não pertence a você) | 404 |
4090 | Limite de endpoints atingido (máx. 2: primary, backup) | 409 |
4091 | A função solicitada já está em uso — alterne com PATCH ou exclua o endpoint existente | 409 |
4220 | Nada para atualizar (PATCH com corpo vazio) | 422 |
5003 | Falha ao criar o endpoint (tente novamente) | 503 |
Limites de taxa
Limitado por chave de API (cabeçalho X-API-KEY):
| Período | Limite |
|---|---|
| 1 segundo | 5 requisições |
| 1 minuto | 150 requisições |
Limite de taxa excedido (429)
json
{ "message": "API rate limit exceeded" }Observações
- O secret é exibido apenas uma vez — na criação e na rotação. Ele nunca é retornado por
GET/LIST. Perdeu? faça a rotação para obter um novo. - Dois endpoints, sem fan-out: um
primarye umbackup. Cada pedido confirmado gera um webhook, entregue ao principal; o de backup é usado apenas se as tentativas do principal se esgotarem. - Alteração de URL sem downtime: registre o novo endereço como
backup, verifique-o e, em seguida, usePATCHpara mudá-lo paraprimary— a troca é atômica. - Pausando:
PATCH … {"is_active": false}interrompe a entrega sem perder as configurações do endpoint. - Apenas eventos de sucesso:
delegation.confirmed,bandwidth.delegated,activation.confirmed. Não existe evento de falha — um pedido com falha ou que sofreu timeout não produz nenhum webhook. - Novos tipos de eventos podem ser adicionados ao longo do tempo. Roteie pelo campo
evente ignore os tipos que você ainda não manipula — você nunca precisa registrar nada novo para começar a recebê-los. - Os hashes são verificados on-chain antes da entrega (veja a observação no topo): um webhook contém apenas hashes que estão todos em um bloco, ou nem sequer é enviado.
- As URLs são validadas quanto à segurança contra SSRF no registro e em cada edição; o mecanismo de entrega as revalida no momento do envio.