En esta página
Guía de integración
Documentación para desarrolladores
Cree una factura, envíe al cliente a la página de pago y reciba un webhook firmado cuando se pague. Empiece con los cuatro pasos siguientes.
Qué se admite
La pasarela acepta USDT y USDC. Esta tabla coincide con GET /v1/services, verificado el 4 de noviembre de 2026. Consulte el catálogo vigente antes de crear su selector de métodos de pago.
| Activo | Red | Confirmaciones |
|---|---|---|
| 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 |
Los pagos son custodiados: los fondos llegan a direcciones controladas por la plataforma. Su saldo de comerciante es el importe que se le adeuda. La billetera de Telegram, un producto aparte, es sin custodia.
Limitaciones
- Los pagos salientes y los reembolsos aún no están activos:
GET /v1/servicesinformaboundaries.livePayoutsyboundaries.liveRefundscomofalse. - Los pagos entrantes no son analizados por un proveedor de KYT.
- Las facturas en las que las comisiones superarían el 20 % del importe se rechazan con
AMOUNT_BELOW_MINIMUM. - Sin SLA contractual.
La comisión inicial es del 0,40 % más una comisión de red. Solicite una cotización de comisiones para conocer el total y el importe mínimo; consulte los precios.
Autenticación
Las llamadas de servidor a servidor usan x-api-key. Las claves de proyecto se limitan a un único proyecto de comerciante. Manténgalas en su servidor.
Las claves de prueba empiezan con ncp_test_…; las claves de modo real empiezan con ncp_live_…. Las claves emitidas anteriormente empiezan con ncp_sandbox_ y siguen funcionando.
curl -sS "https://api.veilnebu.la/v1/balances" \
-H "x-api-key: $PROJECT_API_KEY"Firmar solicitudes de escritura
Las solicitudes GET solo necesitan x-api-key. Cada POST, PUT, PATCH y DELETE autenticado con una clave de proyecto también necesita dos encabezados; de lo contrario, la API responde 401 Invalid API signature:
x-ncp-timestamp: ISO-8601, con una diferencia máxima de 5 minutos respecto de la hora actual.x-ncp-signature:hex(HMAC-SHA256(your API key, "<timestamp>.<METHOD>.<path incl. query>.<raw body>")).
Use los métodos en mayúsculas. Firme exactamente los bytes que envía, incluida la cadena de consulta de la ruta. Una solicitud sin cuerpo firma una cadena vacía. Las funciones auxiliares siguientes requieren su clave de proyecto; la de shell requiere Bash, curl y OpenSSL, y la de Node.js usa 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
}Crear su primera factura
Cree un proyecto en el panel, copie su clave de API y cree una factura. La respuesta contiene checkoutUrl: envíe allí a su cliente.
ncp POST /v1/payments '{
"amountMinor":"10000",
"currencyCode":"USD",
"settlementAssetCode":"USDT",
"externalId":"order-1001"
}' -H "idempotency-key: order-1001"Extracto de la respuesta. El ID se genera para cada factura.
{
"id": "cm3k9x2f40001qz8e7h5v1abc",
"status": "new",
"amountMinor": "10000",
"currencyCode": "USD",
"settlementAssetCode": "USDT",
"checkoutUrl": "https://app.veilnebu.la/checkout/cm3k9x2f40001qz8e7h5v1abc"
}amountMinor es una cadena con un número entero en la unidad más pequeña de la moneda: 10000 = 100.00 USD. El campo decimal amount solo lo acepta el endpoint de navegador /v1/public/payments.
Página de pago desde el navegador y orígenes permitidos
Cree una clave publicable en el panel y enumere los orígenes exactos que usa su tienda. Las solicitudes del navegador la envían como x-publishable-key a POST /v1/public/payments.
El origen, successUrl y cancelUrl deben figurar en la lista de orígenes permitidos de la clave. Un origen es el esquema, el host y el puerto opcional, sin ruta ni comodines. Los orígenes ausentes o no incluidos en la lista se rechazan.
El importe decimal admite hasta dos decimales para monedas fiduciarias y hasta los decimales propios del activo para criptomonedas (USDT y USDC: 6 en todas las redes que admitimos actualmente).
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);Estados de pago
| Estado | Siguiente acción |
|---|---|
new | Compartir el enlace de la página de pago |
pending | Esperando el pago |
confirming | Esperando confirmaciones |
paid | Liquidación lista |
partially_paid | Pedir al cliente que complete el pago |
overpaid | Revisar el importe adicional acreditado |
expired | Crear una nueva factura |
cancelled | No se requiere ninguna acción |
failed | Abrir detalles |
refunded | No se requiere ninguna acción |
Claves de idempotencia
Para crear facturas, envíe un Idempotency-Key estable derivado del ID de su pedido. Un reintento con la misma clave y el mismo cuerpo devuelve la factura original. Reutilizarla con un cuerpo distinto devuelve 409. Mantenga la clave y el cuerpo sin cambios al reintentar después de un tiempo de espera agotado.
Consulte el ejemplo de la primera factura para ver la solicitud.
Errores
Los errores son JSON. message es una cadena, o un array de cadenas en los errores de validación.
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid API key"
}| Estado HTTP | Significado y acción |
|---|---|
400 | Campo no válido; "message" indica el campo. Corríjalo antes de reintentar. |
401 | "Invalid API key" o "Invalid API signature". Revise la clave, los bytes firmados y la marca de tiempo. |
404 | "Payment invoice not found". Revise el ID y el proyecto. |
409 | El mismo "Idempotency-Key" con un cuerpo distinto, o "Insufficient merchant payable balance". |
429 | Límite de solicitudes alcanzado. Espere antes de reintentar. |
5xx | Reintente con el mismo "Idempotency-Key" y el mismo cuerpo. |
Webhooks (HMAC-SHA256)
Cada entrega incluye x-nebula-signature: t=<unix>,v1=<hex>. Verifique el HMAC con el secreto del endpoint que se devolvió al crearlo. Rechace las marcas de tiempo que se alejen más de 300 segundos de la hora actual.
Verificar en Node.js
El verificador de Node.js usa solo el módulo crypto integrado. Pásele el encabezado de firma, los bytes del cuerpo sin procesar y el secreto del 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 con el cuerpo sin procesar de la solicitud, antes de analizar el JSON. La entrega es de al menos una vez: elimine duplicados según x-nebula-event-id, que coincide con eventId del payload. Los reintentos automáticos conservan el ID de entrega; un reenvío manual crea un nuevo ID de entrega con el mismo ID de evento. Guarde el evento antes de responder con 2xx.
Probar webhooks en local
Las URL de webhook deben usar un host público de internet. Las direcciones locales y privadas se rechazan. Exponga su receptor mediante un túnel HTTPS público y registre su URL con la función auxiliar de firma.
ncp POST /v1/webhooks/endpoints '{
"url":"https://receiver.example/webhook",
"events":["payment.paid","payment.reorged"]
}'Reemplace la URL por la de su receptor. Guarde el secreto del endpoint que se devuelve al crearlo.
Tipos de eventos
Los pagos salientes y los reembolsos aún no están activos (GET /v1/services informa boundaries.livePayouts como false). Hasta entonces, los pagos salientes y los lotes de pagos salientes solo registran la transferencia en su saldo; payout.completed y refund.completed no significan que los fondos hayan salido.
El catálogo siguiente coincide con GET /v1/services. Que un evento figure en el catálogo no significa que el movimiento de fondos correspondiente esté activo.
| Evento | Significado |
|---|---|
webhook. | Una entrega de prueba solicitada para un endpoint de webhook. |
payment. | El cliente eligió un método de pago; se espera el primer depósito. |
payment. | Depósito detectado; esperando las confirmaciones requeridas. |
payment. | Factura pagada por completo y liquidada en el libro contable. |
payment. | Se recibió menos de lo solicitado; esperando el resto. |
payment. | Se recibió más de lo solicitado; liquidada en el libro contable con el pago excedente. |
payment. | La factura venció sin fondos suficientes. |
payment. | Factura cancelada antes del pago. |
payment. | El pago falló por un error de la cadena, de la política de comisiones o de observación. |
payment. | Evento de riesgo informativo para un depósito ya acreditado. El análisis KYT de pagos entrantes no está activo. |
payment. | Una reorganización de la cadena descartó un depósito confirmado; la factura ya no está pagada, así que retenga o revierta el cumplimiento del pedido. |
payment. | Se depositó un token no admitido; no se acreditó. |
payout. | Estado de procesamiento del pago saliente. Las transferencias reales no están habilitadas. |
payout. | Pago saliente registrado en el saldo; esto no significa que los fondos hayan salido. |
payout. | El pago saliente falló. |
payout. | Pago saliente cancelado antes de completarse. |
refund. | Estado de procesamiento del reembolso. Los reembolsos reales no están habilitados. |
refund. | Reembolso registrado en el saldo; esto no significa que los fondos hayan salido. |
refund. | El reembolso falló. |
internal_transfer. | Transferencia de saldo entre comerciantes confirmada para el emisor. |
internal_transfer. | Transferencia de saldo entre comerciantes acreditada al receptor. |
static_wallet. | Se creó una dirección de depósito permanente para un cliente. |
static_wallet. | Se bloqueó una billetera estática. |
static_wallet. | Se levantó el bloqueo de una billetera estática. |
static_wallet. | Se detectó un depósito en una billetera estática y se liquidó en el libro contable. |
dispute. | Se abrió una disputa sobre una factura. |
dispute. | Se resolvió una disputa. La resolución por sí sola no demuestra un reembolso en la cadena. |
dispute. | Se rechazó una disputa; el comerciante no necesita hacer nada. |
subscription. | Un ciclo de facturación creó una nueva factura. |
Política de reintentos
Si su endpoint no responde con 2xx, lo intentamos 8 veces en total: el intento inicial y luego reintentos al cabo de 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas, 12 horas y 24 horas. Cada espera empieza después del intento fallido anterior. Los errores de red y los tiempos de espera agotados también cuentan como fallos. Las entregas de prueba tienen un solo intento.
Tras el último fallo, la entrega se marca como fallida; reenvíela con POST /v1/webhooks/deliveries/{deliveryId}/resend. Como alternativa, concilie con GET /v1/payments/{id}.
Los pagos salientes y los reembolsos aún no están activos (GET /v1/services informa boundaries.livePayouts como false). Hasta entonces, los pagos salientes y los lotes de pagos salientes solo registran la transferencia en su saldo; payout.completed y refund.completed no significan que los fondos hayan salido.
Lotes de pagos salientes
Un lote agrupa registros de transferencias. Su política de aprobación y el número de aprobaciones requeridas se devuelven como approvalPolicy y approvalsRequired. Cuando hay varios aprobadores, el creador no puede aprobar su propio lote. Un único operador habilitado puede aprobar su propio lote creado en sesión. Apruebe los lotes con WebAuthn en el panel.
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"Reemplace el destino por una dirección válida en su propio proyecto de prueba.
Facturación recurrente
Una suscripción crea una nueva factura en cada ciclo de facturación y la notifica con subscription.invoice_created. No debita automáticamente la billetera del cliente. Envíe al cliente la nueva URL de la página de pago.
interval: DAY, WEEK, MONTH o YEAR.
ncp POST /v1/subscriptions '{
"customerRef":"customer-42",
"assetCode":"USDT",
"networkCode":"TRON",
"amountMinor":"9000000",
"interval":"MONTH",
"intervalCount":1
}'Pause, reanude o cancele mediante las operaciones de ciclo de vida descritas en la referencia de la API.
Control de riesgos
Los destinos de pagos salientes y reembolsos se comparan con listas públicas de sanciones antes de crear la transferencia; una coincidencia rechaza la solicitud. Los pagos entrantes no se analizan.
Bot de Telegram para comerciantes
@veilnebula_bot trabaja con un proyecto a la vez. Vincúlelo en un chat privado con la clave de API de ese proyecto. El bot intenta eliminar su mensaje y cifra la clave almacenada. Si no logra eliminarlo, revoque la clave y genere una nueva. Nunca publique una clave en un chat compartido.
Las notificaciones se limitan al proyecto vinculado y a la clave activa. Abra el panel para aprobar pagos salientes.
/link <API_KEY>
/balance
/invoices
/notify on
/unlink