API Nova para Desenvolvedores
Receba PIX, gere links de pagamento e ouça webhooks a partir do seu próprio app. A API é previsível: chave Bearer no cabeçalho, JSON na entrada, JSON na resposta.
Nesta página
- Contrato da API
- Início rápido
- Pacote SDK
- Autenticação
- Chamadas do navegador (CORS)
- Limites de taxa
- Idempotência
- Erros
- Endpoints
- Sistema
- GET Verificação de vida (liveness)
- GET Round-trip autenticado
- Cobranças PIX
- POST Criar uma cobrança PIX (QR code)
- GET Consultar uma cobrança PIX
- Links de pagamento
- POST Criar um link de pagamento reutilizável
- GET Consultar um link de pagamento
- DELETE Pausar um link de pagamento
- Webhooks
- GET Listar webhooks
- POST Assinar eventos
- DELETE Cancelar a assinatura
- POST Disparar uma entrega de teste
- Webhooks
- Versionamento
- Bug bounty
- Suporte
- llms.md
Contrato de produção da API #
A v1 da Nova é pequena de propósito. Integre seguindo estas regras:
- Um caminho base: toda rota autenticada fica em
/api/v1. - Um tipo de auth: envie
Authorization: Bearer <chave>. Não envie chaves de API em código de navegador. - Uma superfície de SDK: SDKs públicos devem chamar apenas
/api/v1/*./api/wallet/*é da SPA Nova logada e usa cookies com CSRF. - Resposta direta em sucesso: respostas com sucesso devolvem o próprio recurso. Erros sempre usam o mesmo envelope
{ "error": ... }. - Retries seguros: POSTs que mexem com dinheiro exigem
Idempotency-Key.X-Idempotency-Keytambém é aceito. - Webhooks assinados: verifique
Nova-Signaturecom o corpo bruto antes de confiar no evento.
Acesso à API #
Você precisa de três coisas:
- Uma conta Nova. Crie uma se ainda não tiver.
- Aprovação para usar a API. Abra o aplicativo Nova, vá em Conta → Desenvolvedor, preencha o pedido e aguarde aprovação (geralmente em menos de um dia útil).
- Uma chave de API. Após aprovação, a mesma tela permite criar uma. Copie o segredo na hora — guardamos só o hash e nunca mostramos de novo.
Teste que está tudo conectado:
curl -H "Authorization: Bearer $NOVA_API_KEY" https://novadao.app/api/v1/ping
Você deve receber um JSON com o id da sua chave, o tier e o horário do servidor. Se vier 401, confira a chave. Se vier 403 falando de domínio, veja Chamadas do navegador (CORS).
Gere a sua primeira cobrança PIX de R$ 1 (use um endereço Liquid de verdade que você controla):
curl -X POST https://novadao.app/api/v1/pix/charges \
-H "Authorization: Bearer $NOVA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount_in_cents": 100,
"depix_address": "lq1..."
}'
Você vai receber um id, uma string qr_copy_paste (cole em qualquer app de banco brasileiro) e uma qr_image_url com a marca da Nova. Pague o QR para ver a cobrança virar paid.
Pacote SDK #
O pacote TypeScript @novadao/sdk é fino: um transporte HTTP, grupos tipados por recurso, erros tipados, idempotência como opção explícita e verificação de webhook incluída. Ele não esconde retries de POST se o chamador não enviou uma chave de idempotência.
Instale pelo npm:
npm install @novadao/sdk
Página do pacote: @novadao/sdk no npm.
type NovaApiError = {
status: number;
code: string;
message: string;
requestId: string;
};
type PixCharge = {
id: string;
status: 'pending' | 'paid' | 'expired' | 'failed';
amountInCents: number | null;
qrCopyPaste: string | null;
qrImageUrl: string | null;
blockchainTxId: string | null;
createdAt: string;
};
type PaymentLink = {
id: string;
name: string;
mode: 'fixed' | 'range' | 'open';
status: 'active' | 'paused';
url: string;
handle: string;
slug: string;
amountBrl: number | null;
minBrl: number | null;
maxBrl: number | null;
depixAddress: string;
createdAt: string;
};
type WebhookEndpoint = {
id: number;
url: string;
events: string[];
status: 'active' | 'disabled';
createdAt: string;
};
type CreatedWebhookEndpoint = WebhookEndpoint & {
signingSecret: string;
};
type NovaClient = {
pix: {
createCharge(
input: { amountInCents: number; depixAddress: string },
options: { idempotencyKey: string },
): Promise<PixCharge>;
getCharge(id: string): Promise<PixCharge>;
};
paymentLinks: {
create(input: CreatePaymentLink, options?: { idempotencyKey?: string }): Promise<PaymentLink>;
get(id: string): Promise<PaymentLink>;
pause(id: string): Promise<PaymentLink>;
};
webhooks: {
list(): Promise<WebhookEndpoint[]>;
create(
input: CreateWebhookEndpoint,
options: { idempotencyKey: string },
): Promise<CreatedWebhookEndpoint>;
remove(id: number): Promise<{ id: number; deleted: true }>;
test(id: number): Promise<{ deliveryId: string; queuedAt: string }>;
verifySignature(
rawBody: string | Uint8Array,
header: string,
secret: string,
options?: { toleranceSeconds?: number },
): Promise<boolean>;
};
};
Uso recomendado:
import { NovaClient, createIdempotencyKey } from '@novadao/sdk';
const nova = new NovaClient({
apiKey: process.env.NOVA_API_KEY!,
});
const charge = await nova.pix.createCharge(
{ amountInCents: 100, depixAddress: 'lq1...' },
{ idempotencyKey: createIdempotencyKey('pix') },
);
Regras de implementação:
- Lance um
NovaApiErrorcomstatus,code,messageerequestIdquando a API devolver envelope de erro. - Tipifique falhas usando
statusecode. O mesmo código pode aparecer com status HTTP diferente. - Repita apenas erros de rede,
408,429e5xx. RespeiteRetry-After. - Nunca repita
POST /pix/chargessem chave de idempotência. Para a mesma requisição lógica, repita com a mesma chave; use chave nova apenas para uma cobrança nova. - Exija chave de idempotência em
webhooks.createno SDK, mesmo que a API crua aceite como opcional. - Aceite snake_case da API, mas exponha tipos camelCase em TypeScript.
Autenticação #
Uma chave de API é uma sequência secreta que prova que o pedido veio de você. Trate como senha: nunca coloque em código que roda no navegador, nunca dê commit no GitHub, e troque se desconfiar que vazou.
Envie sua chave no cabeçalho Bearer padrão:
Authorization: Bearer nv_live_8aXk3rT9...
X-Api-Key. Enviar isso retorna 401 auth_use_bearer apontando para cá.O que significa “aprovação”?
A API faz coisas reais — gera cobranças PIX, cria links de pagamento — então aprovamos cada conta antes que qualquer chave possa ser criada. O formulário no aplicativo pergunta o que você está construindo e qual volume espera. A maioria dos pedidos é revisada em até um dia útil.
Lista de IPs permitidos
Se você cadastrar uma lista de IPs na sua conta de desenvolvedor (também em Conta → Desenvolvedor), a API rejeita qualquer requisição cujo IP de origem não esteja na lista. É opcional, mas muito recomendado para servidores em produção.
Chamadas do navegador (CORS) #
Por padrão a API da Nova aceita apenas chamadas servidor-a-servidor. Se você tentar chamar /api/v1/* de um navegador, a resposta é 403 browser_origin_disallowed.
Isso é intencional. Uma chave de API no navegador é uma chave de API no devtools de cada visitante. Se você realmente precisa chamar do navegador (por exemplo, uma integração híbrida onde a mesma chave assina chamadas servidor e cliente), pode cadastrar domínios permitidos específicos para essa chave. O painel mostra um aviso bem claro quando você faz isso.
Limites de taxa #
Dois orçamentos, ambos por chave de API:
- Por minuto — 60 (tier1, padrão) ou 600 (tier2, sob demanda).
- Por dia — 10.000 (tier1) ou 200.000 (tier2).
Endpoints específicos têm sublimites próprios — veja as observações de cada endpoint abaixo. Toda resposta autenticada traz:
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 47 X-RateLimit-Reset: 1714000060 X-RateLimit-Daily-Limit: 10000 X-RateLimit-Daily-Remaining: 9842 X-RateLimit-Daily-Reset: 1714086400
No 429 rate_limited você também recebe Retry-After (em segundos). Espere e tente de novo — não fique em loop.
Idempotência #
Falhas de rede acontecem. Se a sua requisição POST /pix/charges der timeout, você não sabe se a cobrança foi criada ou não. Chaves de idempotência tornam a re-tentativa segura.
Envie um cabeçalho Idempotency-Key único (1..255 caracteres ASCII, qualquer coisa que você gerar) em todo POST que possa ser repetido. Se a mesma chave chegar duas vezes com o mesmo corpo, devolvemos a resposta cacheada com X-Idempotent-Replay: true em vez de criar uma segunda cobrança. Se a mesma chave chegar com um corpo diferente, devolvemos 409 idempotency_key_reused para você saber que algo está estranho.
Respostas cacheadas vivem por 24 horas. Erros do servidor (5xx) nunca são cacheados, então você sempre pode repetir uma falha transitória com a mesma chave.
- Obrigatório em
POST /pix/charges(dinheiro de verdade). - Opcional em
POST /payment-linksePOST /webhooks. Mande uma se quiser repetir com segurança.
Erros #
Todos os erros compartilham o mesmo envelope:
{
"error": {
"code": "rate_limited",
"message": "You are sending requests too quickly. Slow down and try again.",
"request_id": "req_8aXk3rT9...",
"docs_url": "https://novadao.app/devs/errors#rate_limited"
}
}
O request_id é único por requisição. Inclua no chamado de suporte — assim achamos o log do servidor na hora.
| HTTP | Código | Significado |
|---|---|---|
| 401 | auth_missing | Nenhum cabeçalho Authorization foi enviado. |
| 401 | auth_use_bearer | Você enviou X-Api-Key. Use Authorization: Bearer <chave> no lugar. |
| 401 | auth_invalid | A chave de API é inválida, revogada ou não aprovada. |
| 403 | browser_origin_disallowed | Requisição do navegador cujo Origin não está em allowed_domains da chave. Chamadas servidor-a-servidor não devem enviar Origin. |
| 403 | ip_not_allowlisted | O IP da requisição não está na lista de IPs permitidos da conta. |
| 429 | rate_limited | Muitas requisições. Leia X-RateLimit-Reset e Retry-After. O esgotamento do limite diário usa o mesmo código. |
| 400 / 409 / 422 | invalid_payload | O corpo, caminho ou operação falhou na validação. SDKs devem guardar o status HTTP e o error.code. |
| 400 | invalid_json | O corpo da requisição está vazio ou não é um JSON válido. |
| 413 | payload_too_large | O corpo da requisição é maior do que o endpoint aceita. |
| 400 | idempotency_key_required | POST /pix/charges exige Idempotency-Key em toda requisição. |
| 409 | idempotency_key_in_progress | Uma requisição com esta Idempotency-Key ainda está processando. Repita a mesma requisição em instantes. |
| 409 | idempotency_key_reused | Você enviou a mesma Idempotency-Key com um corpo diferente. Gere uma nova chave para novas requisições. |
| 404 | not_found | O recurso não existe ou não é seu. |
| 503 | payment_service_unavailable | O provedor de pagamento está temporariamente indisponível. Repita a mesma requisição lógica com a mesma Idempotency-Key. |
| 500 | internal_error | Algo deu errado do nosso lado. |
Endpoints #
URL base: https://novadao.app. Toda rota autenticada está sob /api/v1/.
Verificação de vida (liveness).
Sem autenticação, sem limite de taxa, sem banco. Retorna 200 enquanto a API estiver rodando. Use em páginas de status e monitores de uptime.
Formato da resposta
Conferir se a API está no ar
cURL
curl https://novadao.app/api/v1/health
Node.js
const res = await fetch('https://novadao.app/api/v1/health');
const data = await res.json();
console.log(data); Python
import httpx
res = httpx.get('https://novadao.app/api/v1/health')
print(res.json()) Retorna
{
"ok": true,
"service": "nova-public-api",
"server_time": "2026-04-25T12:34:56.000Z"
} Round-trip autenticado.
Confirma que sua chave de API funciona e mostra o que o servidor vê: o id da chave, o tier, os domínios permitidos e o horário do servidor. O botão "testar conexão" do painel chama este endpoint.
Parâmetros
- Authorization obrigatório
Bearer <sua-chave-de-api>
Formato da resposta
Validar sua chave
cURL
curl -H "Authorization: Bearer $NOVA_API_KEY" \ https://novadao.app/api/v1/ping
Node.js
const res = await fetch('https://novadao.app/api/v1/ping', {
headers: { Authorization: `Bearer ${process.env.NOVA_API_KEY}` },
});
console.log(await res.json()); Python
import os, httpx
res = httpx.get(
'https://novadao.app/api/v1/ping',
headers={'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}'},
)
print(res.json()) Retorna
{
"ok": true,
"key_id": 42,
"tier": "tier1",
"allowed_domains": [],
"server_time": "2026-04-25T12:34:56.000Z",
"request_id": "req_8aXk..."
} Criar uma cobrança PIX (QR code).
Gera um QR code PIX que o seu cliente pode pagar. Preste atenção a dois cabeçalhos: Authorization e Idempotency-Key. A Idempotency-Key é OBRIGATÓRIA — ela permite repetir uma chamada após erro de rede sem cobrar duas vezes.
Limite de taxa: tier1: 30/min · tier2: 300/min
Parâmetros
- Authorization obrigatório
Bearer <sua-chave-de-api>
- Idempotency-Key obrigatório
Um valor único que você gera por requisição (1..255 caracteres ASCII). `X-Idempotency-Key` também é aceito.
- amount_in_cents obrigatório
Valor da cobrança em centavos (BRL). Mínimo 100 (R$ 1,00).
- depix_address obrigatório
Endereço confidencial da rede Liquid (`lq1...`) que vai receber os DePix quando a cobrança liquidar.
Formato da resposta
Criar uma cobrança de R$ 50
cURL
curl -X POST https://novadao.app/api/v1/pix/charges \
-H "Authorization: Bearer $NOVA_API_KEY" \
-H "Idempotency-Key: order-1234" \
-H "Content-Type: application/json" \
-d '{
"amount_in_cents": 5000,
"depix_address": "lq1..."
}' Node.js
import { randomUUID } from 'node:crypto';
const res = await fetch('https://novadao.app/api/v1/pix/charges', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOVA_API_KEY}`,
'Idempotency-Key': randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount_in_cents: 5000,
depix_address: 'lq1...',
}),
});
const charge = await res.json();
console.log(charge.id, charge.qr_copy_paste); Python
import os, uuid, httpx
res = httpx.post(
'https://novadao.app/api/v1/pix/charges',
headers={
'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
'amount_in_cents': 5000,
'depix_address': 'lq1...',
},
)
charge = res.json()
print(charge['id'], charge['qr_copy_paste']) Retorna
{
"id": "ch_a8b3...",
"status": "pending",
"amount_in_cents": 5000,
"qr_copy_paste": "00020126580014BR.GOV.BCB.PIX...",
"qr_image_url": "https://novadao.app/api/v1/pix/charges/ch_a8b3.../qr.png",
"blockchain_tx_id": null,
"created_at": "2026-04-25T12:34:56.000Z"
} Consultar uma cobrança PIX.
Retorna o status atual de uma cobrança PIX. Assine `pix.charge.received` quando precisar da confirmação bancária do pagamento, e `pix.charge.paid` quando precisar da transação liquidada na Liquid.
Parâmetros
- id obrigatório
O id da cobrança retornado por POST /pix/charges.
Formato da resposta
Conferir se a cobrança foi paga
cURL
curl -H "Authorization: Bearer $NOVA_API_KEY" \ https://novadao.app/api/v1/pix/charges/ch_a8b3
Node.js
const res = await fetch('https://novadao.app/api/v1/pix/charges/ch_a8b3', {
headers: { Authorization: `Bearer ${process.env.NOVA_API_KEY}` },
});
const charge = await res.json();
console.log(charge.status); Python
import os, httpx
res = httpx.get(
'https://novadao.app/api/v1/pix/charges/ch_a8b3',
headers={'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}'},
)
charge = res.json()
print(charge['status']) Retorna
{
"id": "ch_a8b3...",
"status": "paid",
"amount_in_cents": 5000,
"qr_copy_paste": "00020126580014BR.GOV.BCB.PIX...",
"qr_image_url": "https://novadao.app/api/v1/pix/charges/ch_a8b3.../qr.png",
"blockchain_tx_id": "7f4a...",
"created_at": "2026-04-25T12:34:56.000Z"
} Criar um link de pagamento reutilizável.
Gera uma URL pública de checkout (`https://novadao.app/c/<handle>/<slug>`) que aceita pagamentos. Três modos: `fixed` (valor único), `range` (mín/máx), `open` (qualquer valor, com limites opcionais).
Limite de taxa: tier1: 60/min
Parâmetros
- name obrigatório
1 a 80 caracteres. Usado para gerar o slug.
- mode obrigatório
Modelo de preço.
- amount_brl
Obrigatório quando mode é "fixed".
- min_brl
Usado quando mode é "range" (obrigatório) ou "open" (opcional).
- max_brl
Usado quando mode é "range" (obrigatório) ou "open" (opcional).
- depix_address obrigatório
Endereço confidencial da rede Liquid (`lq1...`) que recebe DePix a cada pagamento bem-sucedido.
- options
Opcional: { ask_name?: bool, ask_email?: bool, thank_you_message?: string, sales_limit?: int }.
Formato da resposta
Vender uma pizza por R$ 25
cURL
curl -X POST https://novadao.app/api/v1/payment-links \
-H "Authorization: Bearer $NOVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Pizza margherita",
"mode": "fixed",
"amount_brl": 25,
"depix_address": "lq1..."
}' Node.js
const res = await fetch('https://novadao.app/api/v1/payment-links', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOVA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Pizza margherita',
mode: 'fixed',
amount_brl: 25,
depix_address: 'lq1...',
}),
});
const link = await res.json();
console.log(link.url); Python
import os, httpx
res = httpx.post(
'https://novadao.app/api/v1/payment-links',
headers={'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}'},
json={
'name': 'Pizza margherita',
'mode': 'fixed',
'amount_brl': 25,
'depix_address': 'lq1...',
},
)
link = res.json()
print(link['url']) Retorna
{
"id": "lk_HQKFuN22r39cc1Dp",
"name": "Pizza margherita",
"mode": "fixed",
"status": "active",
"url": "https://novadao.app/c/myshop/pizza-margherita",
"handle": "myshop",
"slug": "pizza-margherita",
"amount_brl": 25,
"min_brl": null,
"max_brl": null,
"depix_address": "lq1...",
"created_at": "2026-04-25T12:34:56.000Z"
} Consultar um link de pagamento.
Retorna o estado atual de um link de pagamento, incluindo a URL pública.
Parâmetros
- id obrigatório
O id do link.
Formato da resposta
Consultar um link antes de exibir
cURL
curl -H "Authorization: Bearer $NOVA_API_KEY" \ https://novadao.app/api/v1/payment-links/lk_HQKFuN22r39cc1Dp
Node.js
const res = await fetch('https://novadao.app/api/v1/payment-links/lk_HQKFuN22r39cc1Dp', {
headers: { Authorization: `Bearer ${process.env.NOVA_API_KEY}` },
});
const link = await res.json();
console.log(link.status, link.url); Python
import os, httpx
res = httpx.get(
'https://novadao.app/api/v1/payment-links/lk_HQKFuN22r39cc1Dp',
headers={'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}'},
)
link = res.json()
print(link['status'], link['url']) Retorna
{
"id": "lk_HQKFuN22r39cc1Dp",
"name": "Pizza margherita",
"mode": "fixed",
"status": "active",
"url": "https://novadao.app/c/myshop/pizza-margherita",
"handle": "myshop",
"slug": "pizza-margherita",
"amount_brl": 25,
"min_brl": null,
"max_brl": null,
"depix_address": "lq1...",
"created_at": "2026-04-25T12:34:56.000Z"
} Pausar um link de pagamento.
Exclusão branda: pausa o link para que ele pare de aceitar pagamentos novos. URLs já distribuídas mostram uma página "pausado" em vez de 404, evitando que clientes em checkout vejam um erro confuso.
Parâmetros
- id obrigatório
O id do link.
Formato da resposta
Pausar um link
cURL
curl -X DELETE https://novadao.app/api/v1/payment-links/lk_HQKFuN22r39cc1Dp \ -H "Authorization: Bearer $NOVA_API_KEY"
Node.js
const res = await fetch('https://novadao.app/api/v1/payment-links/lk_HQKFuN22r39cc1Dp', {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.NOVA_API_KEY}` },
});
const link = await res.json();
console.log(link.status); Python
import os, httpx
res = httpx.delete(
'https://novadao.app/api/v1/payment-links/lk_HQKFuN22r39cc1Dp',
headers={'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}'},
)
link = res.json()
print(link['status']) Retorna
{
"id": "lk_HQKFuN22r39cc1Dp",
"name": "Pizza margherita",
"mode": "fixed",
"status": "paused",
"url": "https://novadao.app/c/myshop/pizza-margherita",
"handle": "myshop",
"slug": "pizza-margherita",
"amount_brl": 25,
"min_brl": null,
"max_brl": null,
"depix_address": "lq1...",
"created_at": "2026-04-25T12:34:56.000Z"
} Listar webhooks.
Retorna todos os webhooks da sua conta.
Formato da resposta
Listar webhooks configurados
cURL
curl -H "Authorization: Bearer $NOVA_API_KEY" \ https://novadao.app/api/v1/webhooks
Node.js
const res = await fetch('https://novadao.app/api/v1/webhooks', {
headers: { Authorization: `Bearer ${process.env.NOVA_API_KEY}` },
});
const data = await res.json();
console.log(data.webhooks.length); Python
import os, httpx
res = httpx.get(
'https://novadao.app/api/v1/webhooks',
headers={'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}'},
)
data = res.json()
print(len(data['webhooks'])) Retorna
{
"webhooks": [
{
"id": 7,
"url": "https://yourshop.com/webhooks/nova",
"events": ["pix.charge.paid", "payment_link.paid"],
"status": "active",
"created_at": "2026-04-25T12:34:56.000Z"
}
]
} Assinar eventos.
Cria um inscrito de webhook. A resposta inclui um `signing_secret` mostrado uma única vez — guarde com cuidado; você vai precisar dele para verificar a assinatura de cada entrega.
Parâmetros
- url obrigatório
Endpoint HTTPS que vai receber as entregas via POST.
- events obrigatório
Um ou mais entre: pix.charge.created, pix.charge.received, pix.charge.paid, pix.charge.expired, pix.charge.failed, payment_link.paid.
Formato da resposta
Assinar cobranças pagas
cURL
curl -X POST https://novadao.app/api/v1/webhooks \
-H "Authorization: Bearer $NOVA_API_KEY" \
-H "Idempotency-Key: webhook-paid-events-v1" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourshop.com/webhooks/nova",
"events": ["pix.charge.paid", "payment_link.paid"]
}' Node.js
const res = await fetch('https://novadao.app/api/v1/webhooks', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOVA_API_KEY}`,
'Idempotency-Key': 'webhook-paid-events-v1',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://yourshop.com/webhooks/nova',
events: ['pix.charge.paid', 'payment_link.paid'],
}),
});
const wh = await res.json();
console.log('Save this signing secret:', wh.signing_secret); Python
import os, httpx
res = httpx.post(
'https://novadao.app/api/v1/webhooks',
headers={
'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}',
'Idempotency-Key': 'webhook-paid-events-v1',
},
json={
'url': 'https://yourshop.com/webhooks/nova',
'events': ['pix.charge.paid', 'payment_link.paid'],
},
)
wh = res.json()
print('Save this signing secret:', wh['signing_secret']) Retorna
{
"id": 7,
"url": "https://yourshop.com/webhooks/nova",
"events": ["pix.charge.paid", "payment_link.paid"],
"status": "active",
"signing_secret": "Rll0hXsdn2hzp1G6...",
"created_at": "2026-04-25T12:34:56.000Z"
} Cancelar a assinatura.
Remove um inscrito de webhook. Entregas já enfileiradas ainda são tentadas.
Parâmetros
- id obrigatório
Id do webhook.
Formato da resposta
Remover um webhook
cURL
curl -X DELETE https://novadao.app/api/v1/webhooks/7 \ -H "Authorization: Bearer $NOVA_API_KEY"
Node.js
const res = await fetch('https://novadao.app/api/v1/webhooks/7', {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.NOVA_API_KEY}` },
});
console.log(await res.json()); Python
import os, httpx
res = httpx.delete(
'https://novadao.app/api/v1/webhooks/7',
headers={'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}'},
)
print(res.json()) Retorna
{
"id": 7,
"deleted": true
} Disparar uma entrega de teste.
Enfileira uma entrega `webhook.test.ping` para a URL do seu webhook. Use para conferir que seu endpoint recebe e verifica assinaturas corretamente. A entrega é assinada igual a um evento real.
Parâmetros
- id obrigatório
Id do webhook.
Formato da resposta
Enviar um evento assinado de teste
cURL
curl -X POST https://novadao.app/api/v1/webhooks/7/test \ -H "Authorization: Bearer $NOVA_API_KEY"
Node.js
const res = await fetch('https://novadao.app/api/v1/webhooks/7/test', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.NOVA_API_KEY}` },
});
const delivery = await res.json();
console.log(delivery.delivery_id); Python
import os, httpx
res = httpx.post(
'https://novadao.app/api/v1/webhooks/7/test',
headers={'Authorization': f'Bearer {os.environ["NOVA_API_KEY"]}'},
)
delivery = res.json()
print(delivery['delivery_id']) Retorna
{
"delivery_id": "whd_abc123",
"queued_at": "2026-04-25T12:34:56.000Z"
} Webhooks: eventos #
A Nova envia um payload JSON para a URL do seu webhook sempre que algo acontece com uma das suas cobranças ou links de pagamento. Assine os eventos que importam quando você criar o webhook.
Para cobranças PIX D+2, use pix.charge.received para confirmar que o pagador concluiu o PIX no trilho bancário. Um pagamento delayed chega com status: "delayed", settlement_status: "delayed", delay_until, confirmed_at e blockchain_tx_id: null. Use pix.charge.paid apenas para a liquidação final na Liquid com blockchain_tx_id, e continue ouvindo pix.charge.failed para cobranças estornadas, canceladas ou com erro.
- pix.charge.created
Uma cobrança PIX acabou de ser criada. Útil para registrar a criação no seu banco de dados.
- pix.charge.received
Um cliente pagou a cobrança PIX no trilho bancário. Cobranças D+2 podem chegar com `status: "delayed"`, `settlement_status: "delayed"`, `delay_until`, `confirmed_at` e `blockchain_tx_id: null`, e liquidar depois via `pix.charge.paid`.
- pix.charge.paid
A cobrança PIX liquidou na Liquid e inclui o id da transação de liquidação.
- pix.charge.expired
Uma cobrança PIX expirou antes de ser paga.
- pix.charge.failed
Uma cobrança PIX falhou (cancelada, estornada ou rejeitada pela rede). Continue ouvindo este evento mesmo depois de `pix.charge.received`.
- payment_link.paid
Um checkout de link de pagamento foi concluído. Dispara junto com `pix.charge.paid` quando a cobrança veio de um link.
Toda entrega inclui estes cabeçalhos:
Nova-Signature: t=1714000000,v1=8a7c3b... Nova-Event: pix.charge.paid Nova-Delivery-Id: whd_abc123 Nova-Api-Version: 2026-04-24
Webhooks: verificar a assinatura #
A gente manda um carimbo; você confere se o carimbo bate antes de confiar na mensagem. Sem verificação, qualquer um que descobrir sua URL pode mandar eventos falsos. O cabeçalho Nova-Signature tem duas partes: t (timestamp Unix em segundos) e v1 (HMAC-SHA256 em hex). A string assinada é t + "." + raw_body. Calcule o HMAC com o seu signing_secret (devolvido uma única vez na criação do webhook) e compare.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyNovaWebhook(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
const t = Number(parts.t);
const v1 = parts.v1;
if (!t || !v1) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSec) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
if (expected.length !== v1.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
} Python
import hmac, hashlib, time
def verify_nova_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split('=', 1) for p in header.split(','))
try:
t = int(parts['t']); v1 = parts['v1']
except KeyError:
return False
if abs(int(time.time()) - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1) PHP
function verify_nova_webhook(string $body, string $header, string $secret, int $tolerance = 300): bool {
parse_str(str_replace(',', '&', $header), $parts);
$t = (int) ($parts['t'] ?? 0); $v1 = $parts['v1'] ?? '';
if (!$t || !$v1) return false;
if (abs(time() - $t) > $tolerance) return false;
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
return hash_equals($expected, $v1);
} Webhooks: política de retry #
Se o seu endpoint responder 2xx em 15 segundos, a entrega é marcada como concluída. Qualquer outra resposta é falha. Falhas tentam de novo seis vezes com espera crescente:
Depois da última tentativa, a entrega é marcada como failed. O painel mostra ela em "Entregas de webhook" com um botão de replay manual. O aplicativo Nova também coloca uma notificação na sua caixa.
Versionamento #
A versão atual é /api/v1/. Tratamos como contrato permanente. Campos e tipos de evento novos são aditivos e não exigem que você mude nada. Se algum dia rolar uma mudança que quebra, ela aparece em /api/v2/ e a v1 fica viva por pelo menos 12 meses. Durante a janela de sunset, toda resposta da v1 carrega o cabeçalho Deprecation: true e uma data Sunset.
Todo payload de webhook também inclui um campo api_version para que assinantes possam ramificar o código sem fixar datas.
Bug bounty e divulgação responsável #
A Nova recebe relatos de segurança sobre superfícies do produto e da API. Envie achados em privado para hello@novadao.app com o assunto [security] <descrição curta>. Não abra issues públicas nem publique detalhes até a Nova confirmar que a divulgação está liberada.
Escopo
- Superfícies de produção da Nova sob
https://novadao.app, incluindo/app,/api/v1/*, fluxos de conta e autenticação, links de pagamento, assinatura e entrega de webhooks, e problemas do site ou da documentação com impacto de segurança. - Relatos testados apenas em contas, chaves, webhooks, links de pagamento e dados que sejam seus ou que você tenha autorização explícita para testar.
Regras
- Inclua descrição clara, impacto, URL ou endpoint afetado, passos para reproduzir e qualquer prova de conceito necessária para validar o problema.
- Mantenha os testes no mínimo necessário. Não acesse, modifique, apague nem extraia dados de outros usuários. Se dados sensíveis aparecerem, pare o teste e reporte imediatamente.
- Pesquisa de boa-fé que segue estas regras está coberta por safe harbor: a Nova não tomará ação legal por testes autorizados dentro deste programa.
Fora de escopo
- Denial-of-service, teste de carga, brute force automatizado, engenharia social, phishing, ataques físicos, spam ou campanhas de abuso.
- Provedores terceirizados, sistemas de parceiros, extensões de navegador, dispositivos de usuários e ambientes que não sejam produção, salvo autorização explícita da Nova.
- Duplicatas conhecidas, saída de scanner sem impacto demonstrado, problemas teóricos sem prova de conceito prática e problemas que afetam apenas navegadores ou plataformas desatualizados.
Recompensas
Relatos validados podem ser elegíveis a recompensas discricionárias. A decisão considera severidade, impacto real, reprodutibilidade, explorabilidade, qualidade do relato e se o problema já era conhecido.
Suporte
Duas formas de falar com a gente:
- Bot de suporte no Telegram — caminho mais rápido, com resposta em até uma hora em horário comercial.
- Inclua o
request_iddo envelope de erro quando escrever. Conseguimos rastrear cada chamada na hora com ele.
Para integrações com LLMs, aponte sua ferramenta para /pt/devs/llms.md — mesmo conteúdo, em formato legível por máquina.