Zum Inhalt
Dokumentation
Auf dieser Seite

Integrationsleitfaden

Entwicklerdokumentation

Rechnung erstellen, den Kunden zur Zahlungsseite schicken, nach der Zahlung einen signierten Webhook erhalten. Beginnen Sie mit den vier Schritten unten.

  1. Kopieren Sie den API-Schlüssel Ihres Projekts.
  2. Signieren Sie Schreibanfragen.
  3. Erstellen Sie Ihre erste Rechnung.
  4. Prüfen Sie den Zahlungs-Webhook.

Was unterstützt wird

Das Gateway akzeptiert USDT und USDC. Diese Tabelle entspricht GET /v1/services, Stand 4. November 2026. Lesen Sie den aktuellen Katalog, bevor Sie Ihre Auswahl der Zahlungsmethoden bauen.

AssetNetzwerkBestätigungen
USDTTron20
USDTEthereum12
USDTSolana32
USDTPolygon8
USDTArbitrum12
USDTTON5
USDCEthereum12
USDCSolana32
USDCPolygon8
USDCBase12
USDCArbitrum12

Zahlungen sind verwahrt (custodial): Die Mittel gehen auf Adressen ein, die von der Plattform kontrolliert werden. Ihr Händlerguthaben ist der Betrag, den wir Ihnen schulden. Die separate Telegram-Wallet ist selbstverwahrt (non-custodial).

Einschränkungen

  • Auszahlungen und Erstattungen sind noch nicht live: GET /v1/services meldet boundaries.livePayouts und boundaries.liveRefunds als false.
  • Eingehende Zahlungen werden nicht von einem KYT-Anbieter geprüft.
  • Rechnungen, bei denen die Gebühren 20 % des Betrags übersteigen würden, werden mit AMOUNT_BELOW_MINIMUM abgelehnt.
  • Kein vertragliches SLA.

Die Einstiegsgebühr beträgt 0,40 % plus Netzwerkgebühr. Fordern Sie ein Gebührenangebot an, um den Gesamtbetrag und den Mindestbetrag zu erfahren; siehe Preise.

Authentifizierung

Server-zu-Server-Aufrufe verwenden x-api-key. Projektschlüssel gelten nur für ein einzelnes Händlerprojekt. Bewahren Sie sie auf Ihrem Server auf.

Testschlüssel beginnen mit ncp_test_…, Live-Schlüssel mit ncp_live_…. Früher ausgestellte Schlüssel beginnen mit ncp_sandbox_; sie funktionieren weiterhin.

curl -sS "https://api.veilnebu.la/v1/balances" \
  -H "x-api-key: $PROJECT_API_KEY"

Schreibanfragen signieren

GET-Anfragen benötigen nur x-api-key. Jede mit einem Projektschlüssel authentifizierte POST-, PUT-, PATCH- und DELETE-Anfrage benötigt zusätzlich zwei Header, sonst antwortet die API mit 401 Invalid API signature:

  • x-ncp-timestamp: ISO-8601, höchstens 5 Minuten von der aktuellen Zeit entfernt.
  • x-ncp-signature: hex(HMAC-SHA256(your API key, "<timestamp>.<METHOD>.<path incl. query>.<raw body>")).

Verwenden Sie Methoden in Großbuchstaben. Signieren Sie genau die Bytes, die Sie senden, einschließlich des Query-Strings im Pfad. Eine Anfrage ohne Body signiert einen leeren String. Die folgenden Helfer benötigen Ihren Projektschlüssel; der Shell-Helfer benötigt Bash, curl und OpenSSL, der Node.js-Helfer verwendet das native fetch.

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
}

Erste Rechnung erstellen

Erstellen Sie im Dashboard ein Projekt, kopieren Sie dessen API-Schlüssel und erstellen Sie eine Rechnung. Die Antwort enthält checkoutUrl: Leiten Sie Ihren Kunden dorthin.

ncp POST /v1/payments '{
  "amountMinor":"10000",
  "currencyCode":"USD",
  "settlementAssetCode":"USDT",
  "externalId":"order-1001"
}' -H "idempotency-key: order-1001"

Auszug aus der Antwort. Die ID wird für jede Rechnung neu erzeugt.

{
  "id": "cm3k9x2f40001qz8e7h5v1abc",
  "status": "new",
  "amountMinor": "10000",
  "currencyCode": "USD",
  "settlementAssetCode": "USDT",
  "checkoutUrl": "https://app.veilnebu.la/checkout/cm3k9x2f40001qz8e7h5v1abc"
}

amountMinor ist ein ganzzahliger String in der kleinsten Einheit der Währung: 10000 = 100.00 USD. Das dezimale Feld amount wird nur vom Browser-Endpunkt /v1/public/payments akzeptiert.

Zahlung im Browser und erlaubte Origins

Erstellen Sie im Dashboard einen veröffentlichbaren Schlüssel und tragen Sie die exakten Origins ein, die Ihr Shop verwendet. Browser-Anfragen senden ihn als x-publishable-key an POST /v1/public/payments.

Origin, successUrl und cancelUrl müssen alle auf der Liste der erlaubten Origins des Schlüssels stehen. Ein Origin besteht aus Schema, Host und optionalem Port, ohne Pfad oder Platzhalter. Fehlende oder nicht eingetragene Origins werden abgelehnt.

Der Dezimalbetrag akzeptiert bei Fiatwährungen bis zu zwei Nachkommastellen und bei Kryptowährungen bis zur Anzahl der Dezimalstellen des jeweiligen Assets (USDT und USDC: 6 in allen Netzwerken, die wir derzeit unterstützen).

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);

Zahlungsstatus

StatusNächster Schritt
newZahlungslink teilen
pendingWarten auf Zahlung
confirmingWarten auf Bestätigungen
paidAbrechnung bereit
partially_paidKunden bitten, die Zahlung abzuschließen
overpaidGutgeschriebenen Mehrbetrag prüfen
expiredNeue Rechnung erstellen
cancelledKeine Aktion erforderlich
failedDetails öffnen
refundedKeine Aktion erforderlich

Idempotenzschlüssel

Senden Sie bei der Rechnungserstellung einen stabilen Idempotency-Key, abgeleitet von Ihrer Bestell-ID. Eine Wiederholung mit demselben Schlüssel und Body liefert die ursprüngliche Rechnung zurück. Wird er mit einem anderen Body wiederverwendet, kommt 409 zurück. Lassen Sie Schlüssel und Body unverändert, wenn Sie nach einem Timeout erneut senden.

Die Anfrage finden Sie im Beispiel für die erste Rechnung.

Fehler

Fehler werden als JSON geliefert. message ist ein String oder bei Validierungsfehlern ein Array von Strings.

{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid API key"
}
HTTP-StatusBedeutung und Maßnahme
400Ungültiges Feld; "message" nennt das Feld. Korrigieren Sie es vor einer Wiederholung.
401Invalid API key oder Invalid API signature. Prüfen Sie den Schlüssel, die signierten Bytes und den Zeitstempel.
404Payment invoice not found. Prüfen Sie die ID und das Projekt.
409Derselbe Idempotency-Key mit anderem Body oder Insufficient merchant payable balance.
429Ratenlimit erreicht. Warten Sie vor einer Wiederholung.
5xxMit demselben Idempotency-Key und Body wiederholen.

Webhooks (HMAC-SHA256)

Jede Zustellung enthält x-nebula-signature: t=<unix>,v1=<hex>. Prüfen Sie den HMAC mit dem Endpunkt-Secret, das Sie beim Anlegen des Endpunkts erhalten haben. Lehnen Sie Zeitstempel ab, die mehr als 300 Sekunden von der aktuellen Zeit abweichen.

Prüfung in Node.js

Der Node.js-Verifier nutzt nur das integrierte crypto-Modul. Übergeben Sie ihm den Signatur-Header, die rohen Body-Bytes und das Endpunkt-Secret.

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));
}

Prüfen Sie gegen den rohen Request-Body, vor dem JSON-Parsing. Die Zustellung erfolgt mindestens einmal: Deduplizieren Sie anhand von x-nebula-event-id, der mit eventId im Payload übereinstimmt. Automatische Wiederholungen behalten die Zustell-ID; ein manuelles erneutes Senden erzeugt eine neue Zustell-ID mit derselben Ereignis-ID. Speichern Sie das Ereignis, bevor Sie mit 2xx antworten.

Webhooks lokal testen

Webhook-URLs müssen einen öffentlichen Internet-Host verwenden. Lokale und private Adressen werden abgelehnt. Machen Sie Ihren Empfänger über einen öffentlichen HTTPS-Tunnel erreichbar und registrieren Sie dann dessen URL mit dem Signatur-Helfer.

ncp POST /v1/webhooks/endpoints '{
  "url":"https://receiver.example/webhook",
  "events":["payment.paid","payment.reorged"]
}'

Ersetzen Sie die URL durch Ihren Empfänger. Speichern Sie das beim Anlegen zurückgegebene Endpunkt-Secret.

Ereignistypen

Auszahlungen und Erstattungen sind noch nicht live (GET /v1/services meldet boundaries.livePayouts als false). Bis dahin erfassen Auszahlungen und Auszahlungsstapel die Überweisung nur in Ihrem Guthaben; payout.completed und refund.completed bedeuten nicht, dass Mittel abgeflossen sind.

Der folgende Katalog entspricht GET /v1/services. Ein Katalogeintrag bedeutet nicht, dass die entsprechende Geldbewegung live ist.

EreignisBedeutung
webhook.testEine für einen Webhook-Endpunkt angeforderte Testzustellung.
payment.pendingDer Kunde hat eine Zahlungsmethode gewählt; die erste Einzahlung steht aus.
payment.confirmingEinzahlung erkannt; Warten auf die erforderlichen Bestätigungen.
payment.paidRechnung vollständig bezahlt und im Ledger verbucht.
payment.partially_paidWeniger als angefordert erhalten; Warten auf den Rest.
payment.overpaidMehr als angefordert erhalten; mit der Überzahlung im Ledger verbucht.
payment.expiredRechnung ohne ausreichende Mittel abgelaufen.
payment.cancelledRechnung vor der Zahlung storniert.
payment.failedZahlung wegen eines Chain-, Gebührenrichtlinien- oder Beobachtungsfehlers fehlgeschlagen.
payment.risk_flaggedHinweisendes Risikoereignis zu einer bereits gutgeschriebenen Einzahlung. Die KYT-Prüfung eingehender Zahlungen ist nicht live.
payment.reorgedEine bestätigte Einzahlung ist durch eine Chain-Reorganisation entfallen; die Rechnung gilt nicht mehr als bezahlt, halten Sie die Auftragsabwicklung daher an oder machen Sie sie rückgängig.
payment.unsupported_assetEin nicht unterstützter Token wurde eingezahlt; er wurde nicht gutgeschrieben.
payout.processingAuszahlung in Bearbeitung. Echte Überweisungen sind nicht freigeschaltet.
payout.completedAuszahlung im Guthaben erfasst; das bedeutet nicht, dass Mittel abgeflossen sind.
payout.failedAuszahlung fehlgeschlagen.
payout.cancelledAuszahlung vor dem Abschluss storniert.
refund.processingErstattung in Bearbeitung. Echte Erstattungen sind nicht freigeschaltet.
refund.completedErstattung im Guthaben erfasst; das bedeutet nicht, dass Mittel abgeflossen sind.
refund.failedErstattung fehlgeschlagen.
internal_transfer.sentGuthabenübertragung zwischen Händlern für den Absender verbucht.
internal_transfer.receivedGuthabenübertragung zwischen Händlern dem Empfänger gutgeschrieben.
static_wallet.createdFür einen Kunden wurde eine langlebige Einzahlungsadresse erstellt.
static_wallet.blockedEine statische Wallet wurde gesperrt.
static_wallet.unblockedDie Sperre einer statischen Wallet wurde aufgehoben.
static_wallet.topup.completedEine Einzahlung auf eine statische Wallet wurde erkannt und im Ledger verbucht.
dispute.openedZu einer Rechnung wurde ein Streitfall eröffnet.
dispute.resolvedEin Streitfall wurde beigelegt. Die Beilegung allein belegt keine On-Chain-Erstattung.
dispute.rejectedEin Streitfall wurde abgelehnt; vom Händler ist nichts zu tun.
subscription.invoice_createdEin Abrechnungszyklus hat eine neue Rechnung erstellt.

Wiederholungsrichtlinie

Wenn Ihr Endpunkt nicht mit 2xx antwortet, versuchen wir es insgesamt 8-mal: der erste Versuch, dann Wiederholungen nach 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden, 6 Stunden, 12 Stunden und 24 Stunden. Jede Wartezeit beginnt nach dem vorangegangenen fehlgeschlagenen Versuch. Netzwerkfehler und Timeouts zählen ebenfalls als Fehlschläge. Testzustellungen haben einen Versuch.

Nach dem letzten Fehlschlag wird die Zustellung als fehlgeschlagen markiert; senden Sie sie mit POST /v1/webhooks/deliveries/{deliveryId}/resend erneut. Gleichen Sie ersatzweise mit GET /v1/payments/{id} ab.

Auszahlungen und Erstattungen sind noch nicht live (GET /v1/services meldet boundaries.livePayouts als false). Bis dahin erfassen Auszahlungen und Auszahlungsstapel die Überweisung nur in Ihrem Guthaben; payout.completed und refund.completed bedeuten nicht, dass Mittel abgeflossen sind.

Auszahlungsstapel

Ein Stapel fasst Überweisungsdatensätze zusammen. Seine Freigaberichtlinie und die Anzahl der erforderlichen Freigaben werden als approvalPolicy und approvalsRequired zurückgegeben. Bei mehreren Freigebenden kann der Ersteller seinen eigenen Stapel nicht freigeben. Ein einziger berechtigter Operator darf einen in seiner eigenen Sitzung erstellten Stapel freigeben. Geben Sie Stapel im Dashboard mit WebAuthn frei.

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"

Ersetzen Sie das Ziel durch eine gültige Adresse in Ihrem eigenen Testprojekt.

Wiederkehrende Abrechnung

Ein Abonnement erstellt in jedem Abrechnungszyklus eine neue Rechnung und meldet sie mit subscription.invoice_created. Es belastet keine Kunden-Wallet automatisch. Senden Sie dem Kunden die neue URL der Zahlungsseite.

interval: DAY, WEEK, MONTH oder YEAR.

ncp POST /v1/subscriptions '{
  "customerRef":"customer-42",
  "assetCode":"USDT",
  "networkCode":"TRON",
  "amountMinor":"9000000",
  "interval":"MONTH",
  "intervalCount":1
}'

Pausieren, fortsetzen oder kündigen Sie mit den Lebenszyklus-Operationen aus der API-Referenz.

Risikoprüfung

Ziele von Auszahlungen und Erstattungen werden vor dem Anlegen der Überweisung mit öffentlichen Sanktionslisten abgeglichen; ein Treffer führt zur Ablehnung der Anfrage. Eingehende Zahlungen werden nicht geprüft.

AML- und Sanktionsrichtlinie

Telegram-Bot für Händler

@veilnebula_bot arbeitet jeweils mit einem Projekt. Verknüpfen Sie ihn in einem privaten Chat mit dem API-Schlüssel dieses Projekts. Der Bot versucht, Ihre Nachricht zu löschen, und speichert den Schlüssel verschlüsselt. Schlägt das Löschen fehl, widerrufen Sie den Schlüssel und ersetzen Sie ihn. Posten Sie einen Schlüssel niemals in einem gemeinsamen Chat.

Benachrichtigungen gelten für das verknüpfte Projekt und den aktiven Schlüssel. Öffnen Sie das Dashboard, um Auszahlungen freizugeben.

/link <API_KEY>
/balance
/invoices
/notify on
/unlink
Integrationsleitfaden · VeilNebula