メインコンテンツへスキップ

概要

Webhookを使用すると、決済の完了、失敗、返金などのイベントをリアルタイムで受け取ることができます。 ZAFA PAYは決済ステータスが変更されると、登録されたWebhookエンドポイントにHTTP POSTリクエストを送信します。
Webhookエンドポイントは、加盟店管理画面(https://app.zafapay.com)の**「Webhook」**タブから管理できます。各エンドポイントには署名検証用の個別のWebhook Secretがあります。

Webhookエンドポイント

複数のWebhookエンドポイントを登録してイベント通知を受け取ることができます。各エンドポイントには個別のURL、シークレット、およびオプションのイベントフィルターがあります。
機能説明
複数エンドポイント加盟店ごとに最大16個のWebhookエンドポイントを登録可能
イベントフィルター各エンドポイントが受信するイベントタイプを選択可能。フィルター未設定の場合、すべてのイベントを受信
エンドポイント別シークレット各エンドポイントに署名検証用の個別のWebhook Secret(whsec_xxx形式)が付与
有効/無効切り替えエンドポイントを削除せずに個別に有効化・無効化可能
Webhookエンドポイントは、加盟店管理画面の**「Webhook」**タブから作成・管理できます。

イベントタイプ

イベント説明
payment.succeeded決済が正常に完了
payment.failed決済が失敗
payment.canceled決済がキャンセル
payment.refunded返金が完了
payment.chargebackチャージバックが発生

ペイロード

event
string
イベントタイプ(例: payment.succeeded
transaction_id
string
トランザクションID
merchant_id
string
加盟店ID
merchant_name
string
加盟店名
status
string
決済ステータス(succeeded, failed, canceled, refunded, chargeback
amount
string
決済金額(文字列形式。例: "100.00"
currency
string
通貨コード
payment_method
string
決済方法(card, depotなど)
payment_method_id
string
保存されたカードのID(pmi_xxx形式)。リカーリング決済またはsave_cardを使用した場合のみ
is_recurring
boolean
リカーリング(継続課金)決済の場合はtrue
save_card
boolean
カードが将来の決済用に保存された場合のみtrueで含まれる
external_id
string
加盟店側の注文ID(決済作成時に指定した値)
product_name
string
商品名(決済作成時に指定した値)
customer_id
string
顧客ID(決済作成時に指定した値)
email
string
顧客のメールアドレス(決済作成時に指定した値)
tel
string
顧客の電話番号(決済作成時に指定した値)
amount_refunded
string
返金済み金額(文字列形式。payment.refundedイベントのみ)
error
string
エラーメッセージ(payment.failedイベントのみ)
card_brand
string
カードブランド(visa, mastercard, amex, jcbなど)
card_last4
string
カード番号の下4桁
cardholder_name
string
カード名義人
card_exp_month
number
カード有効期限(月)
card_exp_year
number
カード有効期限(年)
metadata
object
決済作成時に指定したメタデータ
created_at
string
トランザクション作成日時(ISO 8601形式)
timestamp
string
Webhook送信日時(ISO 8601形式)

ペイロード例

payment.succeeded(決済成功)

{
  "event": "payment.succeeded",
  "transaction_id": "tx_abc123",
  "merchant_id": "acct_12345",
  "merchant_name": "サンプルストア",
  "status": "succeeded",
  "amount": "100.00",
  "currency": "usd",
  "payment_method": "card",
  "payment_method_id": "pmi_abc123",
  "is_recurring": false,
  "save_card": true,
  "external_id": "order_12345",
  "product_name": "プレミアムプラン",
  "customer_id": "cust_12345",
  "email": "customer@example.com",
  "tel": "09012345678",
  "card_brand": "visa",
  "card_last4": "4242",
  "cardholder_name": "TARO YAMADA",
  "card_exp_month": 12,
  "card_exp_year": 2028,
  "metadata": {},
  "created_at": "2024-01-15T10:30:00Z",
  "timestamp": "2024-01-15T10:31:00Z"
}
  • payment_method_idsave_cardが有効な場合、またはリカーリング決済の場合のみ含まれます
  • save_cardは初回決済でカードを保存した場合のみtrueで含まれます
  • is_recurringはリカーリング決済の場合trueになります

payment.failed(決済失敗)

{
  "event": "payment.failed",
  "transaction_id": "tx_abc123",
  "merchant_id": "acct_12345",
  "merchant_name": "サンプルストア",
  "status": "failed",
  "amount": "100.00",
  "currency": "usd",
  "payment_method": "card",
  "is_recurring": false,
  "external_id": "order_12345",
  "product_name": "プレミアムプラン",
  "customer_id": "cust_12345",
  "email": "customer@example.com",
  "tel": "09012345678",
  "error": "Your card was declined.",
  "card_brand": "visa",
  "card_last4": "4242",
  "cardholder_name": "TARO YAMADA",
  "card_exp_month": 12,
  "card_exp_year": 2028,
  "metadata": {},
  "created_at": "2024-01-15T10:30:00Z",
  "timestamp": "2024-01-15T10:31:00Z"
}
errorフィールドはpayment.failedイベントでのみ含まれます。

payment.refunded(返金完了)

{
  "event": "payment.refunded",
  "transaction_id": "tx_abc123",
  "merchant_id": "acct_12345",
  "merchant_name": "サンプルストア",
  "status": "refunded",
  "amount": "100.00",
  "currency": "usd",
  "payment_method": "card",
  "is_recurring": false,
  "external_id": "order_12345",
  "product_name": "プレミアムプラン",
  "customer_id": "cust_12345",
  "email": "customer@example.com",
  "tel": "09012345678",
  "amount_refunded": "50.00",
  "card_brand": "visa",
  "card_last4": "4242",
  "cardholder_name": "TARO YAMADA",
  "card_exp_month": 12,
  "card_exp_year": 2028,
  "metadata": {},
  "created_at": "2024-01-15T10:30:00Z",
  "timestamp": "2024-01-20T14:00:00Z"
}
amount_refundedフィールドはpayment.refundedイベントでのみ含まれます。部分返金の場合は返金済み金額が設定されます。

payment.chargeback(チャージバック発生)

{
  "event": "payment.chargeback",
  "transaction_id": "tx_abc123",
  "merchant_id": "acct_12345",
  "merchant_name": "サンプルストア",
  "status": "chargeback",
  "amount": "100.00",
  "currency": "usd",
  "payment_method": "card",
  "is_recurring": false,
  "external_id": "order_12345",
  "product_name": "プレミアムプラン",
  "customer_id": "cust_12345",
  "email": "customer@example.com",
  "tel": "09012345678",
  "card_brand": "visa",
  "card_last4": "4242",
  "cardholder_name": "TARO YAMADA",
  "card_exp_month": 12,
  "card_exp_year": 2028,
  "metadata": {},
  "created_at": "2024-01-15T10:30:00Z",
  "timestamp": "2024-02-01T09:00:00Z"
}
チャージバックが発生した場合、取引金額が加盟店から引き落とされる可能性があります。速やかに対応してください。

署名検証

Webhookリクエストには署名ヘッダーが含まれます。エンドポイントのWebhook Secretを使用して署名を検証することで、リクエストがZAFA PAYから送信されたことを確認できます。

署名ヘッダー

環境ヘッダー名
SandboxX-Zafapay-Signature-Sandbox
ProductionX-Zafapay-Signature

検証方法

署名はリクエストボディ(JSON文字列)を、エンドポイントのWebhook Secretをキーとして HMAC-SHA256でハッシュ化したものです。
Node.js
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return signature === expectedSignature;
}

// Express.jsでの使用例
app.post('/webhooks/zafapay', express.json(), (req, res) => {
  const signature = req.headers['x-zafapay-signature-sandbox'];
  // このエンドポイントのWebhook Secret(whsec_xxx形式)を使用
  const secret = process.env.ZAFAPAY_WEBHOOK_SECRET;

  if (!verifyWebhookSignature(req.body, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // イベント処理
  const { event, transaction_id, status } = req.body;

  switch (event) {
    case 'payment.succeeded':
      // 決済成功時の処理
      break;
    case 'payment.failed':
      // 決済失敗時の処理
      break;
    case 'payment.refunded':
      // 返金時の処理
      break;
  }

  res.json({ received: true });
});

レスポンス

Webhookを正常に受信した場合は、HTTPステータスコード 2xx を返してください。
{
  "received": true
}

リトライ

ZAFA PAYは以下の場合にWebhookを自動的にリトライします:
  • HTTPステータスコード 2xx 以外が返された場合
  • 接続タイムアウトが発生した場合(10秒)

リトライスケジュール

試行回数失敗後の待機時間
1回目即時送信
2回目1分後
3回目5分後
4回目30分後
5回目2時間後
6回目6時間後
合計6回のリトライが約9時間にわたって実行されます。すべてのリトライが失敗した場合、イベントはデッドレターキュー(DLQ)に移動され、手動での対応が必要になります。
リトライによる重複通知を防ぐため、transaction_idをキーにして冪等性を確保してください。

ベストプラクティス

1

署名を必ず検証する

不正なリクエストを防ぐため、署名検証は必須です
2

冪等性を確保する

同じイベントが複数回送信される可能性があるため、transaction_idをキーにして重複処理を防いでください
3

すぐにレスポンスを返す

Webhook処理は非同期で行い、すぐに 200 を返してください。処理に時間がかかるとタイムアウトでリトライが発生します
4

エラーをログに記録する

デバッグのため、受信したペイロードと処理結果をログに記録してください