Ir al contenido
Documentación
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.

  1. Copie la clave de API de su proyecto.
  2. Firme las solicitudes de escritura.
  3. Cree su primera factura.
  4. Verifique el webhook del pago.

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.

ActivoRedConfirmaciones
USDTTron20
USDTEthereum12
USDTSolana32
USDTPolygon8
USDTArbitrum12
USDTTON5
USDCEthereum12
USDCSolana32
USDCPolygon8
USDCBase12
USDCArbitrum12

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

EstadoSiguiente acción
newCompartir el enlace de la página de pago
pendingEsperando el pago
confirmingEsperando confirmaciones
paidLiquidación lista
partially_paidPedir al cliente que complete el pago
overpaidRevisar el importe adicional acreditado
expiredCrear una nueva factura
cancelledNo se requiere ninguna acción
failedAbrir detalles
refundedNo 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 HTTPSignificado y acción
400Campo 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.
409El mismo "Idempotency-Key" con un cuerpo distinto, o "Insufficient merchant payable balance".
429Límite de solicitudes alcanzado. Espere antes de reintentar.
5xxReintente 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.

EventoSignificado
webhook.testUna entrega de prueba solicitada para un endpoint de webhook.
payment.pendingEl cliente eligió un método de pago; se espera el primer depósito.
payment.confirmingDepósito detectado; esperando las confirmaciones requeridas.
payment.paidFactura pagada por completo y liquidada en el libro contable.
payment.partially_paidSe recibió menos de lo solicitado; esperando el resto.
payment.overpaidSe recibió más de lo solicitado; liquidada en el libro contable con el pago excedente.
payment.expiredLa factura venció sin fondos suficientes.
payment.cancelledFactura cancelada antes del pago.
payment.failedEl pago falló por un error de la cadena, de la política de comisiones o de observación.
payment.risk_flaggedEvento de riesgo informativo para un depósito ya acreditado. El análisis KYT de pagos entrantes no está activo.
payment.reorgedUna 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.unsupported_assetSe depositó un token no admitido; no se acreditó.
payout.processingEstado de procesamiento del pago saliente. Las transferencias reales no están habilitadas.
payout.completedPago saliente registrado en el saldo; esto no significa que los fondos hayan salido.
payout.failedEl pago saliente falló.
payout.cancelledPago saliente cancelado antes de completarse.
refund.processingEstado de procesamiento del reembolso. Los reembolsos reales no están habilitados.
refund.completedReembolso registrado en el saldo; esto no significa que los fondos hayan salido.
refund.failedEl reembolso falló.
internal_transfer.sentTransferencia de saldo entre comerciantes confirmada para el emisor.
internal_transfer.receivedTransferencia de saldo entre comerciantes acreditada al receptor.
static_wallet.createdSe creó una dirección de depósito permanente para un cliente.
static_wallet.blockedSe bloqueó una billetera estática.
static_wallet.unblockedSe levantó el bloqueo de una billetera estática.
static_wallet.topup.completedSe detectó un depósito en una billetera estática y se liquidó en el libro contable.
dispute.openedSe abrió una disputa sobre una factura.
dispute.resolvedSe resolvió una disputa. La resolución por sí sola no demuestra un reembolso en la cadena.
dispute.rejectedSe rechazó una disputa; el comerciante no necesita hacer nada.
subscription.invoice_createdUn 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.

Política AML y de sanciones

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
Guía de integración · VeilNebula