開発者向け 限定パイロット
OpenAPIサポートダッシュボード
API v1 非カストディ型として設計

ステーブルコイン決済。
資金の管理はそのまま。

MainPay は正確な金額の請求を作成し、事業者が管理するウォレットを監視して、オンチェーン送金を照合し、署名付き Webhook を送信します。実際の資金を動かさずに全フローをテストできます。

MainPay は資金を預かりません。売上確定、出金、分配、エスクロー、ゲートウェイによる返金は行いません。支払いは事業者が管理するウォレットに直接決済されます。

すぐに組み込める決済

サーバーで作成。支払いは顧客自身のウォレットで。

REST API は標準の fetch で利用でき、SDK は不要です。フロントエンドには公開決済 URL だけを渡します。クレジットのチャージでは、invoice.paid の検証後にアプリが残高を管理・更新します。

typescript
// Server only — MAINPAY_API_KEY never goes to the browser.
const response = await fetch("https://api.mainpay.com/v1/invoices", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.MAINPAY_API_KEY,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    customer_name: "user_42",
    description: "2,500 AI credits",
    fiat_amount: "25.00",
    asset: "USDC",
    network: "base",
    receiving_account_id: process.env.MAINPAY_WALLET_ID,
    client_reference_id: "user_42",
    metadata: { credits: 2500 },
    purpose: "credit_topup"
  })
});
if (!response.ok) throw new Error(`MainPay error: ${response.status}`);
const order = await response.json();

// Return only order.hosted_url to the browser.

ホスト型決済画面は、正確な ERC-20 送金を支払者自身のウォレットで署名・送信するよう案内します。MainPay は鍵に触れず、決済資金やクレジット残高も保有しません。

クイックスタート

テストキーから支払い済みの請求まで

開始前に Base の USDC ウォレットを接続し、mp_test_… キーを作成して、ダッシュボードで公開 HTTPS Webhook を設定してください。サンプルには curljq が必要です。

01

環境を設定

連携中はテストモードを使用してください。テストオブジェクトは本番の決済・会計から構造的に分離されています。

bash
export MAINPAY_API_BASE=https://api.mainpay.com/v1
export MAINPAY_API_KEY=mp_test_xxxxxxxxxxxxxxxxxxxxxxxx
export MAINPAY_WALLET_ID=1de70609-a510-4fb9-8691-61a8d2c21a34
02

受取ウォレットを確認

請求予定の通貨・ネットワークに一致する有効なウォレットの public_id を選びます。

bash
curl --fail-with-body -s "$MAINPAY_API_BASE/wallets" \
  -H "X-API-Key: $MAINPAY_API_KEY" | jq
03

請求を作成

個別の作成リクエストごとに一意の冪等性キーを使用します。通信エラーで再試行する場合は、同じキーと本文を再利用してください。

bash
export IDEMPOTENCY_KEY="quickstart-$(date +%s)-$RANDOM"

ORDER_JSON=$(
  curl --fail-with-body -sX POST "$MAINPAY_API_BASE/invoices" \
    -H "X-API-Key: $MAINPAY_API_KEY" \
    -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    -H "Content-Type: application/json" \
    -d "$(jq -n --arg wallet "$MAINPAY_WALLET_ID" '{
      description: "API services - quickstart",
      fiat_amount: "500.00",
      asset: "USDC",
      network: "base",
      receiving_account_id: $wallet,
      customer_name: "Acme Corp",
      customer_email: "ap@acme.example",
      client_reference_id: "builder-user-42",
      metadata: { credits: 2500 },
      purpose: "credit_topup"
    }')"
)

export INVOICE_ID=$(printf '%s' "$ORDER_JSON" | jq -r '.public_id')
printf '%s\n' "$ORDER_JSON" | jq
201{ "public_id": "…", "livemode": false, "status": "pending", "crypto_amount": "500.00", "hosted_url": "https://mainpay.com/pay/…" }
04

検出と決済をシミュレーション

シミュレーターは本番決済と同じ状態遷移・イベントサービスを使用しますが、実際のオンチェーン証跡は記録しません。

bash
curl --fail-with-body -sX POST \
  "$MAINPAY_API_BASE/test/invoices/$INVOICE_ID/pay" \
  -H "X-API-Key: $MAINPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scenario":"payment_detected"}' | jq

curl --fail-with-body -sX POST \
  "$MAINPAY_API_BASE/test/invoices/$INVOICE_ID/pay" \
  -H "X-API-Key: $MAINPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scenario":"exact"}' | jq
05

正式な状態を取得

Webhook は通知です。連携先で最新の状態が必要な場合は、請求を取得してください。

bash
curl --fail-with-body -s \
  "$MAINPAY_API_BASE/invoices/$INVOICE_ID" \
  -H "X-API-Key: $MAINPAY_API_KEY" | jq

任意で使える便利なツール

型付き Node SDK を利用。

npm i @mainpay/node@mainpay/node v0.1.0 をインストールします。同じ REST API をラップしており、上記の標準 fetch のサンプルも正式にサポートされる連携方法です。

typescript
// Optional typed convenience: @mainpay/node v0.1.0
// npm i @mainpay/node
// Server only — MAINPAY_API_KEY never goes to the browser.
import MainPay from "@mainpay/node";
const mainpay = new MainPay(process.env.MAINPAY_API_KEY);

const order = await mainpay.orders.create({
  customer_name: "user_42",
  description: "2,500 AI credits",
  fiat_amount: "25.00",
  asset: "USDC",
  network: "base",
  receiving_account_id: process.env.MAINPAY_WALLET_ID,
  client_reference_id: "user_42",
  metadata: { credits: 2500 },
  purpose: "credit_topup"
});

// Return only order.hosted_url to the browser.
// import { openCheckout } from "@mainpay/node/checkout";
// openCheckout(order.hosted_url);

// In your webhook, verify raw bytes before granting access:
const event = mainpay.webhooks.constructEvent(
  rawBody, signatureHeader, process.env.MAINPAY_WEBHOOK_SECRET
);
if (event.type === "invoice.paid") {
  await grantCreditsOnce(event.id, event.data.invoice);
}

認証

1 つのキー、1 つのワークスペース、1 つのモード

mp_test_…

テストキー

分離されたテスト請求を作成し、すべての支払い結果をシミュレーションして、livemode:false イベントを受信します。

mp_live_…

本番キー

自分のウォレットへの実際の決済を監視します。本番アクセスはテストパイロットの合格後にのみ有効になります。

http
X-API-Key: mp_test_xxxxxxxxxxxxxxxxxxxxxxxx

キーは一度だけ表示され、ハッシュ化して保存されます。権限範囲が設定され、即時失効が可能で、ワークスペースとモードごとに分離されています。ブラウザーのコードに公開しないでください。

エンドポイントリファレンス

必要な機能に絞った API

POST/invoices請求を作成201
GET/invoicesカーソルページネーションで請求一覧を取得200
GET/invoices/{public_id}請求の正式な状態を取得200
POST/invoices/{public_id}/cancel未決済の請求をキャンセル200
GET/checkout/{public_id}支払者向けに情報を制限した決済画面を取得200
POST/checkout/{public_id}/events匿名の決済ステップを記録202
GET/wallets有効な受取ウォレット一覧を取得200
POST/test/invoices/{public_id}/payテスト支払いをシミュレーション200
OpenAPI 3.1 仕様YAML をダウンロード →

API の動作

決済業務に適した安全な初期設定

01

小数の文字列

金額は常に文字列でシリアライズし、浮動小数点数は使いません。

02

冪等な作成

同一の再試行では元の請求を返します。本文を変更すると 409 を返します。

03

カーソルページネーション

一覧は datahas_morenext_cursor を返します。

04

キーごとの制限

ローリングウィンドウで毎分 100 リクエスト、バーストは毎秒 20 リクエストまでです。

05

リクエストの識別

各レスポンスにはサポートと追跡用の X-Request-Id が含まれます。

06

支払者の情報を保護する決済画面

公開決済画面には顧客のメールアドレス、内部 ID、支払い履歴を含めません。

Webhook

先に検証。処理は一度だけ。

MainPay は t=<unix>,v1=<hmac> を用いてリクエスト本文の元のバイト列に署名します。古いタイムスタンプを拒否し、定時間比較を行い、安定したイベントの id で重複を排除してください。

javascript
import crypto from "node:crypto";

export function verifyMainPaySignature(rawBody, header, secret) {
  try {
    const parts = Object.fromEntries(
      header.split(",").map((part) => part.split("=", 2))
    );
    const timestamp = Number(parts.t);
    if (!Number.isFinite(timestamp) ||
        Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

    const expected = crypto.createHmac("sha256", secret)
      .update(`${timestamp}.`).update(rawBody).digest("hex");
    const supplied = parts.v1 ?? "";
    const a = Buffer.from(expected);
    const b = Buffer.from(supplied);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  } catch {
    return false;
  }
}
1 元のバイト列を読む2 署名を検証3 イベント ID を保存4 速やかに 2xx を返す

イベントの種類

invoice.created請求の支払い準備が完了
invoice.payment_detected一致する送金の承認待ち
invoice.paid正確な金額または受け入れ可能な支払いが確定
invoice.underpaid確定額が受入基準を下回った
invoice.overpaid確定額が請求額を超えた
invoice.expired未決済の請求が期限切れ
invoice.cancelled事業者が回収を停止

イベントは最低 1 回配信され、順序は保証されません。再試行には永続化されたバックオフを使用します。埋め込まれた請求はスナップショットとして扱い、直接取得した状態を正式な情報としてください。

エラー

予測可能なレスポンス構造

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "not_found",
    "message": "Invoice not found",
    "param": null
  }
}
400無効なリクエストリクエストを修正してから再試行してください。
401認証API キーが設定され、有効であることを確認してください。
403権限正しいモードと認証情報の権限範囲を使用してください。
404見つかりませんオブジェクトが存在しないか、このワークスペース・モードの範囲外です。
409競合同じキーと本文で冪等なリクエストを再試行してください。
422検証フィールドごとのエラー一覧を確認してください。
429レート制限Retry-After の時間を待ち、ランダムな遅延を加えて再試行してください。
503作成を一時停止中Retry-After に従ってください。取得処理は引き続き利用できます。

限定パイロット

連携のテストを始めますか?

ウォレットを接続して Webhook を設定し、ワークスペースのテスト認証情報を有効にするよう MainPay に依頼してください。テスト結果の確認後、本番アクセスが有効になります。

パイロットの利用を申請ダッシュボードを開く