イベントと Webhook
イベントと Webhook の検証方法についての説明
イベントとは
イベント(Event)は、PAY.JP のリソース(決済、顧客、返金など)の状態が変化した際に、システムが自動的に作成します。
たとえば、以下のような場面でイベントが作成されます
| 場面 | イベント |
|---|---|
| 顧客が決済を完了した | payment_flow.succeeded |
| 新しいクレジットカードが登録された | payment_method.attached |
| 返金が処理された | refund.created |
| Checkout Session が完了した | checkout.session.completed |
イベントは以下の3つの目的で使用されます
- Webhook を通じて、加盟店のシステムに状態変化を即座に通知するリアルタイム通知
- すべての取引の完全な履歴を保持するログ
- PAY.JP と外部システムを連携させる基盤としての統合連携
いつイベントを使うか
1. 決済完了の通知
シナリオ: 顧客が商品を購入し、決済が完了したときに注文を確定したい
app.post('/webhook', (req, res) => {
const event = req.body;
if (event.type === 'payment_flow.succeeded') {
const paymentFlow = event.data;
// 注文を確定する
fulfillOrder(paymentFlow.metadata.order_id);
// 顧客にメールを送信
sendConfirmationEmail(paymentFlow.customer);
}
res.json({ received: true });
});受信するイベント
payment_flow.succeeded- 決済成功payment_flow.payment_failed- 決済失敗payment_flow.requires_action- 3Dセキュア認証が必要
2. 決済失敗時のリカバリー
シナリオ: 決済が失敗したときに顧客に通知し、再試行を促したい
if (event.type === 'payment_flow.payment_failed') {
const paymentFlow = event.data;
// 顧客に決済失敗を通知
notifyPaymentFailure(
paymentFlow.customer,
paymentFlow.last_payment_error.message
);
// 管理画面で失敗理由を記録
logPaymentFailure(paymentFlow);
}3. 顧客情報の同期
シナリオ: PAY.JP の顧客情報と自社データベースを同期したい
switch (event.type) {
case 'customer.created':
createCustomerInDatabase(event.data);
break;
case 'customer.updated':
updateCustomerInDatabase(event.data);
break;
case 'customer.deleted':
deleteCustomerInDatabase(event.data.id);
break;
}4. 返金処理の追跡
シナリオ: 返金が処理されたときに在庫を戻したい
if (event.type === 'refund.created') {
const refund = event.data;
// 在庫を戻す
restoreInventory(refund.payment_flow);
// 顧客に返金通知メールを送信
sendRefundNotification(refund);
}Webhook の検証
PAY.JP からのすべての Webhook には X-Payjp-Webhook-Token がヘッダーに含まれています。この値はアカウント固有のトークン ID となっています。
X-Payjp-Webhook-Token: whook_xxxxxxxxxxxxxWebhook を受け取るアプリケーション側で、この値が正しいかどうかを判別することで、PAY.JP からの Webhook であるかを確認できます。
自分のアカウントの X-Payjp-Webhook-Token を確認するには、PAY.JP ダッシュボードのアカウント設定から確認できます。
実装例
import express from 'express';
const app = express();
app.use(express.json());
const WEBHOOK_TOKEN = process.env.PAYJP_WEBHOOK_TOKEN;
app.post('/webhook', (req, res) => {
// Webhook Tokenの検証
const receivedToken = req.headers['x-payjp-webhook-token'];
if (receivedToken !== WEBHOOK_TOKEN) {
return res.status(401).json({ error: 'Invalid webhook token' });
}
const event = req.body;
// イベントタイプに応じた処理
switch (event.type) {
case 'checkout.session.completed':
handleCheckoutSessionCompleted(event.data);
break;
case 'payment_flow.succeeded':
handlePaymentFlowSucceeded(event.data);
break;
// 他のイベントタイプ...
}
// 200ステータスコードを返す
res.json({ received: true });
});
app.listen(3000);Webhook が届かない場合の確認事項
Webhook が正常に届かない場合は、以下を確認してください
- Webhook URL が正しく設定されているか
- エンドポイントが公開アクセス可能か(ローカルホストなどの開発環境の場合は PAY.JP CLI を使用してください)
- ダッシュボードの「テストイベント送信」機能でテストする
リクエスト形式
HTTP ヘッダー
Content-Type: application/json; charset=utf-8
X-Payjp-Webhook-Token: whook_xxxxxxxxxxxxxリクエストボディ
{
"id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
"object": "event",
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:30:00.000Z",
"livemode": true,
"type": "payment_flow.succeeded",
"pending_webhooks": 1,
"data": {
"id": "pfw_xxxxxxxxxxxxxxxxxxxxxxxx",
"object": "payment_flow",
"status": "succeeded",
"amount": 1000,
"currency": "jpy",
"payment_method_types": ["card"],
"capture_method": "automatic",
...
}
}フィールド
id: イベントIDobject: 常にeventcreated_at: イベント作成日時 (UTC, ISO 8601)updated_at: イベント更新日時 (UTC, ISO 8601)livemode: 本番環境かどうか (true: 本番, false: テスト)type: イベントの種類pending_webhooks: 配信完了していない Webhook の数(2xx ステータスコードが得られていない)data: イベントに関連するリソースオブジェクト
イベントタイプ
Customer
customer.created- 顧客作成customer.updated- 顧客更新customer.deleted- 顧客削除
PaymentFlow
payment_flow.created- 作成payment_flow.succeeded- 成功payment_flow.canceled- キャンセルpayment_flow.amount_capturable_updated- キャプチャ可能金額更新payment_flow.payment_failed- 支払い失敗payment_flow.requires_action- 追加アクション必要(3Dセキュアなど)payment_flow.processing- 処理中
SetupFlow
setup_flow.created- 作成setup_flow.succeeded- 成功setup_flow.canceled- キャンセルsetup_flow.requires_action- 追加アクション必要setup_flow.setup_failed- セットアップ失敗
PaymentMethod
payment_method.attached- 顧客に紐付けpayment_method.detached- 紐付け解除payment_method.updated- 更新
Checkout Session
checkout.session.completed- 完了checkout.session.expired- 期限切れ
Refund
refund.created- 返金作成refund.updated- 返金更新refund.failed- 返金失敗
Product / Price / TaxRate
product.created/product.updated/product.deletedprice.created/price.updated/price.deletedtax_rate.created/tax_rate.updated
その他
dispute.created- 不正懸念取引term.created/term.closed- 集計区間statement.created- 取引明細balance.created/balance.fixed/balance.closed/balance.merged- 残高
Webhook エンドポイントの実装要件
1. 2xx ステータスコードを返す
PAY.JP は配信成功の判定に HTTP ステータスコードを使用します。2xx 以外を返すと、PAY.JPは自動的にリトライします。
2. 10秒以内に応答する
タイムアウトした場合、配信失敗とみなされリトライされます。
リトライポリシー
配信に失敗した場合(2xx以外のHTTPステータスコード、タイムアウト10秒、接続エラー)、PAY.JP は3分間隔で、最大3回まで自動的にリトライします。 すべてのリトライが失敗した場合、配信は停止します。
すべてのリトライが失敗した場合、pending_webhooks の値は減少しません。
Events API で配信状況を確認
イベント一覧を取得
curl https://api.pay.jp/v2/events \
-H "Authorization: Bearer YOUR_API_KEY"特定のイベントを取得
curl https://api.pay.jp/v2/events/evt_xxxxx \
-H "Authorization: Bearer YOUR_API_KEY"pending_webhooks フィールド
pending_webhooks フィールドは、設定した Webhook URL への通知が完了していない(2xxのレスポンスが得られていない)Webhook の数を表します。
| pending_webhooks の値 | 説明 |
|---|---|
| 0 | すべての Webhook が正常に配信された |
| 1以上 | まだ配信完了していない Webhook がある |
pending_webhooks が減らない場合
値が減らない原因として以下が考えられます
- 2xx以外のステータスコードを返している
- タイムアウト(10秒以上かかっている)
- エラーが発生している