本文へ移動
ドキュメント
このページ

導入ガイド

開発者向けドキュメント

請求書を作成し、顧客を支払いページへ案内し、支払いが完了したら署名付き Webhook を受け取ります。まずは次の 4 つのステップから始めてください。

  1. プロジェクトの API キーをコピーします。
  2. 書き込みリクエストに署名します。
  3. 最初の請求書を作成します。
  4. 支払いの Webhook を検証します。

対応内容

ゲートウェイは USDT と USDC を受け付けます。この表は GET /v1/services と一致しており、2026年11月4日に確認したものです。支払い方法の選択画面を作る前に、最新のカタログを参照してください。

資産ネットワーク承認数
USDTTron20
USDTEthereum12
USDTSolana32
USDTPolygon8
USDTArbitrum12
USDTTON5
USDCEthereum12
USDCSolana32
USDCPolygon8
USDCBase12
USDCArbitrum12

支払いはカストディ型です。資金はプラットフォームが管理するアドレスに着金します。販売者残高は、当社からお客様へ支払うべき金額です。別製品の Telegram ウォレットはノンカストディ型です。

制限事項

  • 出金と返金はまだ提供していません:GET /v1/services は boundaries.livePayouts と boundaries.liveRefunds を false と報告します。
  • 受信した支払いは KYT プロバイダーによるスクリーニングを受けていません。
  • 手数料が金額の 20% を超える請求書は AMOUNT_BELOW_MINIMUM で拒否されます。
  • 契約上の SLA はありません。

初期の手数料は 0.40% とネットワーク手数料です。合計額と最低金額は手数料の見積もりで確認してください。料金をご覧ください。

認証

サーバー間の呼び出しには x-api-key を使用します。プロジェクトキーは 1 つの販売者プロジェクトに限定されます。キーはサーバー上で保管してください。

テストキーは ncp_test_… で、本番キーは ncp_live_… で始まります。以前に発行されたキーは ncp_sandbox_ で始まり、引き続き使用できます。

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

書き込みリクエストへの署名

GET リクエストに必要なのは x-api-key だけです。プロジェクトキーで認証するすべての POST、PUT、PATCH、DELETE には、さらに 2 つのヘッダーが必要です。ない場合、API は 401 Invalid API signature を返します:

  • x-ncp-timestamp:ISO-8601 形式。現在時刻から 5 分以内。
  • x-ncp-signature:hex(HMAC-SHA256(your API key, "<timestamp>.<METHOD>.<path incl. query>.<raw body>"))。

メソッドは大文字で指定します。パス内のクエリ文字列を含め、送信するバイト列そのものに署名してください。本文のないリクエストでは空文字列に署名します。以下のヘルパーにはプロジェクトキーが必要です。シェル用ヘルパーには Bash、curl、OpenSSL が必要で、Node.js 用ヘルパーはネイティブの 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
}

最初の請求書を作成

ダッシュボードでプロジェクトを作成し、その API キーをコピーして、請求書を作成します。レスポンスには checkoutUrl が含まれます。顧客をそこへ案内してください。

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

レスポンスの抜粋です。ID は請求書ごとに生成されます。

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

amountMinor は通貨の最小単位で表した整数の文字列です:10000 = 100.00 USD。小数の amount フィールドは、ブラウザ用エンドポイント /v1/public/payments でのみ受け付けます。

ブラウザでの支払いと許可するオリジン

ダッシュボードで公開可能キーを作成し、ストアフロントが使用するオリジンを正確に登録してください。ブラウザからのリクエストは、このキーを x-publishable-key として POST /v1/public/payments に送信します。

Origin、successUrl、cancelUrl はすべて、キーの許可オリジン一覧に含まれている必要があります。オリジンとはスキーム、ホスト、および任意のポートのことで、パスやワイルドカードは含みません。オリジンがない場合や一覧にない場合は拒否されます。

小数の金額は、法定通貨では小数点以下 2 桁まで、暗号資産では各資産の小数桁数まで受け付けます(USDT と USDC:現在対応しているすべてのネットワークで 6 桁)。

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

支払いステータス

ステータス次のアクション
new支払いリンクを共有
pending支払いを待機中
confirming承認を待機中
paid決済の準備完了
partially_paid顧客に支払いの完了を依頼
overpaid超過入金額を確認
expired新しい請求書を作成
cancelled対応不要
failed詳細を開く
refunded対応不要

冪等キー

請求書を作成する際は、注文 ID から導いた固定の Idempotency-Key を送信してください。同じキーと本文で再試行すると、元の請求書が返されます。同じキーを異なる本文で再利用すると 409 が返されます。タイムアウト後に再試行する場合は、キーと本文を変えないでください。

リクエストについては最初の請求書の例をご覧ください。

エラー

エラーは JSON で返されます。message は文字列で、検証エラーの場合は文字列の配列です。

{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid API key"
}
HTTP ステータス意味と対処
400無効なフィールドです。message にフィールド名が示されます。修正してから再試行してください。
401Invalid API key または Invalid API signature。キー、署名したバイト列、タイムスタンプを確認してください。
404Payment invoice not found。ID とプロジェクトを確認してください。
409同じ Idempotency-Key で本文が異なるか、Insufficient merchant payable balance です。
429レート制限中です。時間をおいて再試行してください。
5xx同じ Idempotency-Key と本文で再試行してください。

Webhook(HMAC-SHA256)

各配信には x-nebula-signature: t=<unix>,v1=<hex> が付きます。エンドポイント作成時に返されたエンドポイントシークレットで HMAC を検証してください。現在時刻から 300 秒を超えて離れたタイムスタンプは拒否してください。

Node.js での検証

Node.js の検証コードは組み込みの crypto モジュールのみを使用します。署名ヘッダー、生の本文のバイト列、エンドポイントシークレットを渡してください。

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

JSON を解析する前の、生のリクエスト本文で検証してください。配信は少なくとも 1 回行われます。x-nebula-event-id で重複を除外してください。これはペイロードの eventId と一致します。自動再試行では配信 ID は変わりません。手動で再送すると、同じイベント ID で新しい配信 ID が作成されます。2xx を返す前にイベントを保存してください。

Webhook をローカルでテストする

Webhook の URL にはインターネット上の公開ホストを使用する必要があります。ローカルアドレスやプライベートアドレスは拒否されます。公開 HTTPS トンネルで受信側を公開し、署名ヘルパーでその URL を登録してください。

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

URL をご自身の受信側に置き換えてください。作成時に返されるエンドポイントシークレットを保存してください。

イベントの種類

出金と返金はまだ提供していません(GET /v1/services は boundaries.livePayouts を false と報告します)。それまでは、出金と出金バッチは残高上で振替を記録するだけです。payout.completed と refund.completed は、資金が送金されたことを意味しません。

以下のカタログは GET /v1/services と一致しています。カタログに項目があっても、対応する資金移動が提供されているとは限りません。

イベント意味
webhook.testWebhook エンドポイントに対してリクエストされたテスト配信。
payment.pending顧客が支払い方法を選択しました。最初の入金を待っています。
payment.confirming入金を検出しました。必要な承認数を待っています。
payment.paid請求書が全額支払われ、台帳上で決済されました。
payment.partially_paid請求額より少ない金額を受領しました。残りを待っています。
payment.overpaid請求額より多い金額を受領しました。超過分を含めて台帳上で決済されました。
payment.expired十分な資金がないまま請求書の有効期限が切れました。
payment.cancelled支払い前に請求書がキャンセルされました。
payment.failedチェーン、手数料ポリシー、または監視のエラーにより支払いが失敗しました。
payment.risk_flagged計上済みの入金に関する参考情報としてのリスクイベント。受信時の KYT スクリーニングは提供していません。
payment.reorged承認済みの入金がチェーンの再編成により取り消されました。請求書は支払い済みではなくなったため、商品の提供を保留するか取り消してください。
payment.unsupported_asset非対応のトークンが入金されました。入金として計上されていません。
payout.processing出金の処理中ステータス。実際の送金は提供していません。
payout.completed出金が残高に記録されました。資金が送金されたことを意味するものではありません。
payout.failed出金に失敗しました。
payout.cancelled完了前に出金がキャンセルされました。
refund.processing返金の処理中ステータス。実際の返金は提供していません。
refund.completed返金が残高に記録されました。資金が送金されたことを意味するものではありません。
refund.failed返金に失敗しました。
internal_transfer.sent販売者間の残高振替が送信側で確定しました。
internal_transfer.received販売者間の残高振替が受信側に入金されました。
static_wallet.created顧客用の長期利用可能な入金アドレスが作成されました。
static_wallet.blocked固定ウォレットがブロックされました。
static_wallet.unblocked固定ウォレットのブロックが解除されました。
static_wallet.topup.completed固定ウォレットへの入金を検出し、台帳上で決済しました。
dispute.opened請求書に対して異議申し立てが行われました。
dispute.resolved異議申し立てが解決しました。解決したことだけでは、オンチェーンでの返金の証明にはなりません。
dispute.rejected異議申し立てが却下されました。販売者の対応は不要です。
subscription.invoice_created請求サイクルにより新しい請求書が作成されました。

再試行ポリシー

エンドポイントが 2xx を返さない場合、合計 8 回試行します。最初の試行の後、1 分、5 分、30 分、2 時間、6 時間、12 時間、24 時間後に再試行します。各待機時間は直前の失敗した試行の後から数えます。ネットワークエラーとタイムアウトも失敗として扱います。テスト配信の試行は 1 回だけです。

最後に失敗すると、その配信は失敗として記録されます。POST /v1/webhooks/deliveries/{deliveryId}/resend で再送してください。代替手段として GET /v1/payments/{id} で照合してください。

出金と返金はまだ提供していません(GET /v1/services は boundaries.livePayouts を false と報告します)。それまでは、出金と出金バッチは残高上で振替を記録するだけです。payout.completed と refund.completed は、資金が送金されたことを意味しません。

出金バッチ

バッチは振替記録をまとめたものです。承認ポリシーと必要な承認数は approvalPolicy と approvalsRequired として返されます。承認者が複数いる場合、作成者は自分のバッチを承認できません。対象となるオペレーターが 1 人だけの場合は、自分のセッションで作成したバッチを承認できます。バッチはダッシュボードで WebAuthn を使って承認してください。

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"

送金先を、ご自身のテストプロジェクトで有効なアドレスに置き換えてください。

定期請求

サブスクリプションは請求サイクルごとに新しい請求書を作成し、subscription.invoice_created で通知します。顧客のウォレットから自動的に引き落とすことはありません。新しい支払いページの URL を顧客に送ってください。

interval:DAY、WEEK、MONTH、YEAR のいずれか。

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

一時停止、再開、キャンセルは、API リファレンスのライフサイクル操作で行えます。

リスクスクリーニング

出金と返金の送金先は、送金を作成する前に公開されている制裁リストと照合されます。一致した場合、リクエストは拒否されます。受信した支払いはスクリーニングされません。

AML および制裁に関するポリシー

Telegram 販売者ボット

@veilnebula_bot は一度に 1 つのプロジェクトで動作します。プライベートチャットで、そのプロジェクトの API キーを使って連携してください。ボットはあなたのメッセージの削除を試み、保存したキーを暗号化します。削除に失敗した場合は、キーを無効化してローテーションしてください。共有チャットには絶対にキーを投稿しないでください。

通知は、連携したプロジェクトと有効なキーに限定されます。出金を承認するにはダッシュボードを開いてください。

/link <API_KEY>
/balance
/invoices
/notify on
/unlink
導入ガイド · VeilNebula