認証の概要
Waffo Pancake は API アクセスに 2 つの認証方式をサポートしています。
| 方式 | ユースケース | 説明 |
|---|
| API Key | サーバー間通信 | RSA-SHA256 署名を使用した永続的な認証 |
| Store Slug | 公開チェックアウトフロー | X-Store-Slug と X-Environment ヘッダーを使用した公開アクセス |
API Key 認証
API Key は RSA-SHA256 署名を使用した永続的なサーバー間認証を提供します。秘密鍵がサーバーの外に出ることはありません。
リクエストヘッダー
X-Merchant-Id: MER_2aUyqjCzEIiEcYMKj7TZtw
X-Timestamp: 1705312200
X-Signature: BASE64_ENCODED_SIGNATURE
Content-Type: application/json
API Key 認証では X-Environment ヘッダーは不要です。 各 API Key は作成時に test または prod のいずれかに紐付けられます。環境は、署名の検証に成功した鍵によって自動的に決定されます。
SDK の使用(推奨)
import { WaffoPancake } from "@waffo/pancake-ts";
const client = new WaffoPancake({
merchantId: process.env.WAFFO_MERCHANT_ID!,
privateKey: process.env.WAFFO_PRIVATE_KEY!,
});
// All requests are automatically signed
const { store } = await client.stores.create({ name: "My Store" });
署名アルゴリズム(手動連携)
SDK を使用しない場合は、RSA-SHA256 リクエスト署名を実装する必要があります。
1. Build the canonical request:
canonicalRequest = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + SHA256_BASE64(BODY)
2. Sign with RSA-SHA256:
signature = RSA-SHA256(canonicalRequest, privateKey)
3. Base64 encode:
X-Signature = Base64(signature)
手動署名の例
const crypto = require('crypto');
const MERCHANT_ID = 'MER_2aUyqjCzEIiEcYMKj7TZtw';
const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
...your private key...
-----END RSA PRIVATE KEY-----`;
async function callApiWithSignature(method, path, body) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyStr = JSON.stringify(body);
const bodyHash = crypto.createHash('sha256').update(bodyStr).digest('base64');
// Build canonical request
const canonicalRequest = `${method}\n${path}\n${timestamp}\n${bodyHash}`;
// RSA-SHA256 sign
const signature = crypto.sign('sha256', Buffer.from(canonicalRequest), PRIVATE_KEY).toString('base64');
const response = await fetch(`https://api.waffo.ai${path}`, {
method,
headers: {
'Content-Type': 'application/json',
'X-Merchant-Id': MERCHANT_ID,
'X-Timestamp': timestamp,
'X-Signature': signature
},
body: bodyStr
});
return response.json();
}
// Usage
const result = await callApiWithSignature('POST', '/v1/actions/store/create-store', {
name: 'My Store'
});
MERCHANT_ID="MER_2aUyqjCzEIiEcYMKj7TZtw"
TIMESTAMP=$(date +%s)
BODY='{"name":"My Store"}'
BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -binary | base64 -w 0)
CANONICAL_REQUEST="POST
/v1/actions/store/create-store
$TIMESTAMP
$BODY_HASH"
# Sign with openssl (requires private_key.pem file)
SIGNATURE=$(echo -n "$CANONICAL_REQUEST" | openssl dgst -sha256 -sign private_key.pem | base64 -w 0)
curl -X POST "https://api.waffo.ai/v1/actions/store/create-store" \
-H "Content-Type: application/json" \
-H "X-Merchant-Id: $MERCHANT_ID" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
-d "$BODY"
import hashlib
import time
import base64
import json
import requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
MERCHANT_ID = 'MER_2aUyqjCzEIiEcYMKj7TZtw'
PRIVATE_KEY = '''-----BEGIN RSA PRIVATE KEY-----
...your private key...
-----END RSA PRIVATE KEY-----'''
def call_api_with_signature(method, path, body):
timestamp = str(int(time.time()))
body_str = json.dumps(body, separators=(',', ':'))
body_hash = base64.b64encode(hashlib.sha256(body_str.encode()).digest()).decode()
# Build canonical request
canonical_request = f"{method}\n{path}\n{timestamp}\n{body_hash}"
# RSA-SHA256 sign
private_key = serialization.load_pem_private_key(PRIVATE_KEY.encode(), password=None)
signature = private_key.sign(canonical_request.encode(), padding.PKCS1v15(), hashes.SHA256())
signature_b64 = base64.b64encode(signature).decode()
response = requests.post(
f'https://api.waffo.ai{path}',
headers={
'Content-Type': 'application/json',
'X-Merchant-Id': MERCHANT_ID,
'X-Timestamp': timestamp,
'X-Signature': signature_b64
},
data=body_str
)
return response.json()
# Usage
result = call_api_with_signature('POST', '/v1/actions/store/create-store', {
'name': 'My Store'
})
秘密鍵のセキュリティ
- クライアントサイドのコードで秘密鍵を公開しないでください
- 秘密鍵をバージョン管理にコミットしないでください
- 秘密鍵は環境変数に保存してください
- 特にチームメンバーの変更後は、定期的に鍵をローテーションしてください
- タイムスタンプはサーバー時刻から 5 分以内 である必要があります
Store Slug 認証
公開チェックアウトフローでは、Store Slug 認証を使用します。これにより、訪問者は API Key 認証情報なしでチェックアウトセッションの作成や公開ストアデータのクエリが可能になります。
リクエストヘッダー
X-Store-Slug: my-awesome-store-k8x2m9ab
X-Environment: test | prod
Content-Type: application/json
使用例
curl -X POST https://api.waffo.ai/v1/actions/checkout/create-session \
-H "X-Store-Slug: my-awesome-store-k8x2m9ab" \
-H "X-Environment: test" \
-H "Content-Type: application/json" \
-d '{"productId": "PROD_4cWAslE1GKkGeaOMl9Vbmy", "productType": "onetime", "currency": "USD"}'
const response = await fetch('https://api.waffo.ai/v1/graphql', {
method: 'POST',
headers: {
'X-Store-Slug': 'my-awesome-store-k8x2m9ab',
'Content-Type': 'application/json',
'X-Environment': 'prod'
},
body: JSON.stringify({
query: `query { store { id name onetimeProducts { id name prices } } }`
})
});
API Key の作成
開発者ページに移動
ダッシュボード → マーチャント → インテグレーション → API Keys に移動します
API Key を作成
「API Key を作成」をクリックして新しい RSA 鍵ペアを生成します。公開鍵は自動的にサーバーに送信されます。
名前と設定
分かりやすい名前(例:「Production Server」)を付け、対象環境(Test または Production)を選択します。
秘密鍵をダウンロード
秘密鍵をすぐにダウンロードしてください。 再度表示されることはありません。
API Key の削除は即座に実行され、取り消しできません。削除された鍵で署名されたリクエストは 401 Unauthorized で失敗します。
認証方式の比較
| 機能 | API Key | Store Slug |
|---|
| サーバーサイドでの使用 | Yes | No |
| クライアントサイドでの使用 | No | Yes |
| X-Environment が必要 | No | Yes |
| Session Token の発行 | Yes | No |
| Checkout Session の作成 | Yes | Yes |
| 商品管理 | Yes | No |
| GraphQL クエリ | Yes | Yes(公開データのみ) |
| 有効期間 | 永続(鍵で制御) | - |
認証エラー
| ステータス | エラー | 解決方法 |
|---|
| 401 | Invalid signature | 署名アルゴリズム、秘密鍵、タイムスタンプの鮮度(5 分ウィンドウ)を確認してください |
| 401 | Invalid or expired token | 再認証するか、有効な API Key を使用してください |
| 403 | Insufficient permissions | そのロールがエンドポイントへのアクセス権を持っているか確認してください |
| 400 | Missing authentication | 必要なヘッダーが含まれていることを確認してください |
{
"data": null,
"errors": [
{
"message": "Invalid signature",
"layer": "gateway"
}
]
}
セキュリティのベストプラクティス
- クライアントサイドのコード、バージョン管理、公開リポジトリで秘密鍵を公開しないでください
- すべての API リクエストに HTTPS を使用してください
- 偽造リクエストを防ぐために Webhook 署名を検証してください
- テストと本番の鍵を分離してください — 環境ごとに異なる鍵を作成してください
- 特にチームメンバーの変更後は、定期的に鍵をローテーションしてください
- ダッシュボードで API 使用状況を監視し、異常な活動がないか確認してください