Nesta página
Guia de integração
Documentação para desenvolvedores
Crie uma fatura, envie o cliente para a página de pagamento e receba um webhook assinado quando ela for paga. Comece pelos quatro passos abaixo.
O que é suportado
O gateway aceita USDT e USDC. Esta tabela corresponde a GET /v1/services, verificado em 4 de novembro de 2026. Consulte o catálogo atualizado antes de criar seu seletor de formas de pagamento.
| Ativo | Rede | Confirmações |
|---|---|---|
| USDT | Tron | 20 |
| USDT | Ethereum | 12 |
| USDT | Solana | 32 |
| USDT | Polygon | 8 |
| USDT | Arbitrum | 12 |
| USDT | TON | 5 |
| USDC | Ethereum | 12 |
| USDC | Solana | 32 |
| USDC | Polygon | 8 |
| USDC | Base | 12 |
| USDC | Arbitrum | 12 |
Os pagamentos são custodiais: os fundos chegam a endereços controlados pela plataforma. Seu saldo de lojista é o valor devido a você. A carteira do Telegram, que é um produto separado, não é custodial.
Limitações
- Pagamentos de saída e reembolsos ainda não estão ativos:
GET /v1/servicesinformaboundaries.livePayoutseboundaries.liveRefundscomofalse. - Os pagamentos recebidos não passam por triagem de um provedor de KYT.
- Faturas em que as taxas excederiam 20% do valor são rejeitadas com
AMOUNT_BELOW_MINIMUM. - Sem SLA contratual.
A taxa inicial é de 0,40% mais uma taxa de rede. Solicite uma cotação de taxas para obter o total e o valor mínimo; veja os preços.
Autenticação
Chamadas entre servidores usam x-api-key. As chaves de projeto são restritas a um único projeto de lojista. Mantenha-as no seu servidor.
As chaves de teste começam com ncp_test_…; as chaves de produção começam com ncp_live_…. As chaves emitidas anteriormente começam com ncp_sandbox_ e continuam funcionando.
curl -sS "https://api.veilnebu.la/v1/balances" \
-H "x-api-key: $PROJECT_API_KEY"Assinar requisições de escrita
Requisições GET precisam apenas de x-api-key. Todo POST, PUT, PATCH e DELETE autenticado com uma chave de projeto também precisa de dois cabeçalhos; caso contrário, a API responde 401 Invalid API signature:
x-ncp-timestamp: ISO-8601, com no máximo 5 minutos de diferença do horário atual.x-ncp-signature:hex(HMAC-SHA256(your API key, "<timestamp>.<METHOD>.<path incl. query>.<raw body>")).
Use métodos em maiúsculas. Assine exatamente os bytes que você envia, incluindo a query string no caminho. Uma requisição sem corpo assina uma string vazia. Os helpers abaixo exigem sua chave de projeto; o helper de shell exige Bash, curl e OpenSSL, e o helper de Node.js usa o fetch nativo.
export API_BASE_URL="https://api.veilnebu.la"
export PROJECT_API_KEY="ncp_test_…"
ncp() {
local method="$1" path="$2" body="" ts sig
shift 2
if [ "$#" -gt 0 ]; then body="$1"; shift; fi
ts=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) || return
sig=$(printf '%s.%s.%s.%s' "$ts" "$method" "$path" "$body" |
openssl dgst -sha256 -hmac "$PROJECT_API_KEY" -hex) || return
sig=${sig##* }
if [ -n "$body" ]; then
curl -sS --request "$method" "$API_BASE_URL$path" \
-H "x-api-key: $PROJECT_API_KEY" \
-H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" \
-H "content-type: application/json" --data-binary "$body" "$@"
else
curl -sS --request "$method" "$API_BASE_URL$path" \
-H "x-api-key: $PROJECT_API_KEY" \
-H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" "$@"
fi
}Criar sua primeira fatura
Crie um projeto no painel, copie a chave de API dele e crie uma fatura. A resposta contém checkoutUrl: envie seu cliente para lá.
ncp POST /v1/payments '{
"amountMinor":"10000",
"currencyCode":"USD",
"settlementAssetCode":"USDT",
"externalId":"order-1001"
}' -H "idempotency-key: order-1001"Trecho da resposta. O ID é gerado para cada fatura.
{
"id": "cm3k9x2f40001qz8e7h5v1abc",
"status": "new",
"amountMinor": "10000",
"currencyCode": "USD",
"settlementAssetCode": "USDT",
"checkoutUrl": "https://app.veilnebu.la/checkout/cm3k9x2f40001qz8e7h5v1abc"
}amountMinor é uma string com um número inteiro na menor unidade da moeda: 10000 = 100.00 USD. O campo decimal amount é aceito apenas pelo endpoint de navegador /v1/public/payments.
Página de pagamento pelo navegador e origens permitidas
Crie uma chave publicável no painel e liste as origens exatas que sua loja usa. As requisições do navegador a enviam como x-publishable-key para POST /v1/public/payments.
A origem, successUrl e cancelUrl devem estar todas na lista de origens permitidas da chave. Uma origem é o esquema, o host e a porta opcional, sem caminho nem curinga. Origens ausentes ou fora da lista são rejeitadas.
O valor decimal aceita até duas casas decimais para moedas fiduciárias e até o número de casas do próprio ativo para cripto (USDT e USDC: 6 em todas as redes que suportamos hoje).
const response = await fetch('https://api.veilnebu.la/v1/public/payments', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-publishable-key': 'pk_test_…',
},
body: JSON.stringify({
amount: '100.00',
currencyCode: 'USD',
settlementAssetCode: 'USDT',
successUrl: 'https://shop.example/thanks',
cancelUrl: 'https://shop.example/cart',
}),
});
if (!response.ok) throw new Error(await response.text());
const invoice = await response.json();
window.location.assign(invoice.checkoutUrl);Status de pagamento
| Status | Próxima ação |
|---|---|
new | Compartilhar o link da página de pagamento |
pending | Aguardando pagamento |
confirming | Aguardando confirmações |
paid | Liquidação pronta |
partially_paid | Pedir ao cliente que conclua o pagamento |
overpaid | Revisar o valor excedente creditado |
expired | Criar uma nova fatura |
cancelled | Nenhuma ação necessária |
failed | Abrir detalhes |
refunded | Nenhuma ação necessária |
Chaves de idempotência
Ao criar faturas, envie um Idempotency-Key estável derivado do ID do seu pedido. Uma nova tentativa com a mesma chave e o mesmo corpo retorna a fatura original. Reutilizá-la com um corpo diferente retorna 409. Mantenha a chave e o corpo inalterados ao tentar novamente após um timeout.
Veja o exemplo da primeira fatura para a requisição.
Erros
Os erros são em JSON. message é uma string ou, em erros de validação, um array de strings.
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid API key"
}| Status HTTP | Significado e ação |
|---|---|
400 | Campo inválido; "message" indica o campo. Corrija-o antes de tentar novamente. |
401 | "Invalid API key" ou "Invalid API signature". Verifique a chave, os bytes assinados e o carimbo de data/hora. |
404 | "Payment invoice not found". Verifique o ID e o projeto. |
409 | O mesmo "Idempotency-Key" com um corpo diferente, ou "Insufficient merchant payable balance". |
429 | Limite de requisições atingido. Aguarde antes de tentar novamente. |
5xx | Tente novamente com o mesmo "Idempotency-Key" e o mesmo corpo. |
Webhooks (HMAC-SHA256)
Cada entrega traz x-nebula-signature: t=<unix>,v1=<hex>. Verifique o HMAC com o segredo do endpoint retornado quando você o criou. Rejeite carimbos de data/hora com mais de 300 segundos de diferença do horário atual.
Verificar em Node.js
O verificador em Node.js usa apenas o módulo crypto nativo. Passe para ele o cabeçalho de assinatura, os bytes brutos do corpo e o segredo do endpoint.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(header, rawBody, secret) {
const parts = {};
for (const piece of (header ?? '').split(',')) {
const separator = piece.indexOf('=');
if (separator <= 0) continue;
parts[piece.slice(0, separator).trim()] = piece.slice(separator + 1).trim();
}
if (!/^[0-9]{1,16}$/.test(parts.t ?? '')) return false;
if (!/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac('sha256', secret)
.update(parts.t + '.')
.update(rawBody)
.digest('hex');
if (expected.length !== parts.v1.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Verifique com o corpo bruto da requisição, antes de fazer o parse do JSON. A entrega é do tipo pelo menos uma vez: elimine duplicatas por x-nebula-event-id, que corresponde ao eventId do payload. As novas tentativas automáticas mantêm o ID da entrega; um reenvio manual cria um novo ID de entrega com o mesmo ID de evento. Persista o evento antes de responder com 2xx.
Testar webhooks localmente
As URLs de webhook devem usar um host público da internet. Endereços locais e privados são rejeitados. Exponha seu receptor por um túnel HTTPS público e registre a URL dele com o helper de assinatura.
ncp POST /v1/webhooks/endpoints '{
"url":"https://receiver.example/webhook",
"events":["payment.paid","payment.reorged"]
}'Substitua a URL pela do seu receptor. Guarde o segredo do endpoint retornado na criação.
Tipos de evento
Pagamentos de saída e reembolsos ainda não estão ativos (GET /v1/services informa boundaries.livePayouts como false). Até lá, pagamentos de saída e lotes de pagamentos de saída apenas registram a transferência no seu saldo; payout.completed e refund.completed não significam que os fundos saíram.
O catálogo abaixo corresponde a GET /v1/services. Um item no catálogo não significa que a movimentação de fundos correspondente esteja ativa.
| Evento | Significado |
|---|---|
webhook. | Uma entrega de teste solicitada para um endpoint de webhook. |
payment. | O cliente escolheu uma forma de pagamento; aguardando o primeiro depósito. |
payment. | Depósito detectado; aguardando as confirmações necessárias. |
payment. | Fatura paga integralmente e liquidada no livro-razão. |
payment. | Recebido menos do que o solicitado; aguardando o restante. |
payment. | Recebido mais do que o solicitado; liquidada no livro-razão com o valor excedente. |
payment. | A fatura expirou sem fundos suficientes. |
payment. | Fatura cancelada antes do pagamento. |
payment. | O pagamento falhou por um erro da blockchain, da política de taxas ou de observação. |
payment. | Evento de risco informativo para um depósito já creditado. A triagem KYT de entradas não está ativa. |
payment. | Um depósito confirmado foi descartado por uma reorganização da blockchain; a fatura deixou de estar paga, então retenha ou reverta o atendimento do pedido. |
payment. | Um token não suportado foi depositado; ele não foi creditado. |
payout. | Estado de processamento do pagamento de saída. Transferências reais não estão disponíveis. |
payout. | Pagamento de saída registrado no saldo; isso não significa que os fundos saíram. |
payout. | O pagamento de saída falhou. |
payout. | Pagamento de saída cancelado antes da conclusão. |
refund. | Estado de processamento do reembolso. Reembolsos reais não estão disponíveis. |
refund. | Reembolso registrado no saldo; isso não significa que os fundos saíram. |
refund. | O reembolso falhou. |
internal_transfer. | Transferência de saldo entre lojistas efetivada para o remetente. |
internal_transfer. | Transferência de saldo entre lojistas creditada ao destinatário. |
static_wallet. | Um endereço de depósito permanente foi criado para um cliente. |
static_wallet. | Uma carteira estática foi bloqueada. |
static_wallet. | O bloqueio de uma carteira estática foi removido. |
static_wallet. | Um depósito em carteira estática foi detectado e liquidado no livro-razão. |
dispute. | Uma disputa foi aberta contra uma fatura. |
dispute. | Uma disputa foi resolvida. A resolução por si só não comprova um reembolso on-chain. |
dispute. | Uma disputa foi rejeitada; nenhuma ação do lojista é necessária. |
subscription. | Um ciclo de cobrança criou uma nova fatura. |
Política de novas tentativas
Se o seu endpoint não responder com 2xx, fazemos 8 tentativas no total: a tentativa inicial e, depois, novas tentativas após 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas, 12 horas e 24 horas. Cada intervalo começa após a tentativa anterior com falha. Erros de rede e timeouts também contam como falhas. Entregas de teste têm uma única tentativa.
Após a última falha, a entrega é marcada como falha; reenvie-a com POST /v1/webhooks/deliveries/{deliveryId}/resend. Como alternativa, concilie com GET /v1/payments/{id}.
Pagamentos de saída e reembolsos ainda não estão ativos (GET /v1/services informa boundaries.livePayouts como false). Até lá, pagamentos de saída e lotes de pagamentos de saída apenas registram a transferência no seu saldo; payout.completed e refund.completed não significam que os fundos saíram.
Lotes de pagamentos de saída
Um lote agrupa registros de transferência. Sua política de aprovação e o número de aprovações exigidas são retornados como approvalPolicy e approvalsRequired. Com vários aprovadores, quem criou o lote não pode aprová-lo. Um único operador habilitado pode aprovar o próprio lote criado na sessão. Aprove lotes com WebAuthn no painel.
export PROJECT_API_KEY="ncp_test_…"
ncp POST /v1/payout-batches '{
"assetCode":"USDT",
"networkCode":"TRON",
"externalId":"batch-1001",
"items":[{
"destinationAddress":"T…",
"amountMinor":"15000000",
"externalRef":"recipient-1001"
}]
}' -H "idempotency-key: batch-1001"Substitua o destino por um endereço válido no seu próprio projeto de teste.
Cobrança recorrente
Uma assinatura cria uma nova fatura a cada ciclo de cobrança e a informa com subscription.invoice_created. Ela não debita a carteira do cliente automaticamente. Envie ao cliente a nova URL da página de pagamento.
interval: DAY, WEEK, MONTH ou YEAR.
ncp POST /v1/subscriptions '{
"customerRef":"customer-42",
"assetCode":"USDT",
"networkCode":"TRON",
"amountMinor":"9000000",
"interval":"MONTH",
"intervalCount":1
}'Pause, retome ou cancele usando as operações de ciclo de vida da referência da API.
Triagem de risco
Os destinos de pagamentos de saída e reembolsos são verificados em listas públicas de sanções antes de a transferência ser criada; uma correspondência rejeita a requisição. Os pagamentos recebidos não passam por triagem.
Bot do Telegram para lojistas
@veilnebula_bot trabalha com um projeto por vez. Vincule-o em um chat privado com a chave de API desse projeto. O bot tenta apagar sua mensagem e criptografa a chave armazenada. Se não conseguir apagar, revogue a chave e gere uma nova. Nunca publique uma chave em um chat compartilhado.
As notificações se limitam ao projeto vinculado e à chave ativa. Abra o painel para aprovar pagamentos de saída.
/link <API_KEY>
/balance
/invoices
/notify on
/unlink