Ir para o conteúdo
Documentação
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.

  1. Copie a chave de API do seu projeto.
  2. Assine as requisições de escrita.
  3. Crie sua primeira fatura.
  4. Verifique o webhook de pagamento.

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.

AtivoRedeConfirmações
USDTTron20
USDTEthereum12
USDTSolana32
USDTPolygon8
USDTArbitrum12
USDTTON5
USDCEthereum12
USDCSolana32
USDCPolygon8
USDCBase12
USDCArbitrum12

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/services informa boundaries.livePayouts e boundaries.liveRefunds como false.
  • 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

StatusPróxima ação
newCompartilhar o link da página de pagamento
pendingAguardando pagamento
confirmingAguardando confirmações
paidLiquidação pronta
partially_paidPedir ao cliente que conclua o pagamento
overpaidRevisar o valor excedente creditado
expiredCriar uma nova fatura
cancelledNenhuma ação necessária
failedAbrir detalhes
refundedNenhuma 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 HTTPSignificado e ação
400Campo 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.
409O mesmo "Idempotency-Key" com um corpo diferente, ou "Insufficient merchant payable balance".
429Limite de requisições atingido. Aguarde antes de tentar novamente.
5xxTente 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.

EventoSignificado
webhook.testUma entrega de teste solicitada para um endpoint de webhook.
payment.pendingO cliente escolheu uma forma de pagamento; aguardando o primeiro depósito.
payment.confirmingDepósito detectado; aguardando as confirmações necessárias.
payment.paidFatura paga integralmente e liquidada no livro-razão.
payment.partially_paidRecebido menos do que o solicitado; aguardando o restante.
payment.overpaidRecebido mais do que o solicitado; liquidada no livro-razão com o valor excedente.
payment.expiredA fatura expirou sem fundos suficientes.
payment.cancelledFatura cancelada antes do pagamento.
payment.failedO pagamento falhou por um erro da blockchain, da política de taxas ou de observação.
payment.risk_flaggedEvento de risco informativo para um depósito já creditado. A triagem KYT de entradas não está ativa.
payment.reorgedUm 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.unsupported_assetUm token não suportado foi depositado; ele não foi creditado.
payout.processingEstado de processamento do pagamento de saída. Transferências reais não estão disponíveis.
payout.completedPagamento de saída registrado no saldo; isso não significa que os fundos saíram.
payout.failedO pagamento de saída falhou.
payout.cancelledPagamento de saída cancelado antes da conclusão.
refund.processingEstado de processamento do reembolso. Reembolsos reais não estão disponíveis.
refund.completedReembolso registrado no saldo; isso não significa que os fundos saíram.
refund.failedO reembolso falhou.
internal_transfer.sentTransferência de saldo entre lojistas efetivada para o remetente.
internal_transfer.receivedTransferência de saldo entre lojistas creditada ao destinatário.
static_wallet.createdUm endereço de depósito permanente foi criado para um cliente.
static_wallet.blockedUma carteira estática foi bloqueada.
static_wallet.unblockedO bloqueio de uma carteira estática foi removido.
static_wallet.topup.completedUm depósito em carteira estática foi detectado e liquidado no livro-razão.
dispute.openedUma disputa foi aberta contra uma fatura.
dispute.resolvedUma disputa foi resolvida. A resolução por si só não comprova um reembolso on-chain.
dispute.rejectedUma disputa foi rejeitada; nenhuma ação do lojista é necessária.
subscription.invoice_createdUm 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.

Política de AML e sanções

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
Guia de integração · VeilNebula