Webhook(ウェブフック)
このページで扱うトピック
Webhooks
概要
Webhookは、アカウント上で特定のイベントが発生した際に、指定したHTTPSエンドポイントへ当社サーバーから送信されるHTTP POSTリクエストです。各リクエストには、トリガーとなったアクションのデータペイロードを含むイベントオブジェクトが含まれます。
Webhookにより、アカウント上で重要なイベントが発生した際に、非同期かつリアルタイムで通知を受け取ることができます。ステータスの変化を確認するためにAPIを繰り返しポーリングする代わりに、イベントが発生した瞬間にWebhookがサーバーへ直接通知をプッシュします。
主なメリット:
- 効率性: 継続的なポーリングが不要になり、不必要なAPIコールとサーバー負荷を軽減します
- リアルタイム更新: 決済完了、返金、異議申し立てなどのイベント発生時に即座に通知を受け取れます
- 自動化: イベント受信時に、注文ステータスの更新・顧客へのメール送信・アカウントの照合など、アプリケーション内のワークフローを即座に自動で実行できます
- 信頼性: アプリケーションがアクティブに更新を確認していない期間中も、重要なイベントを見逃しません
設定
テストモードとライブモードのWebhookエンドポイントをそれぞれ設定してください:
イベントデータの確認
Webhookのイベントデータは以下の方法で確認できます:
- ダッシュボードの「Events」セクション。右側ペインの Account アイコンをクリックし、Events を選択してください。
- Events API
シリアライゼーション
POSTリクエスト内(またはEvents API経由で返される)のイベントオブジェクトは、トリガーとなったリクエストで送信された Omise-Version ヘッダーの値に関わらず、常にそのイベント発生時点のデフォルトAPIバージョンに従ってシリアライズされます。
例
アカウントのバージョンが 2017-11-02 に設定されている場合:
-H "Omise-Version: 2019-05-29"を指定したcurlでチャージを作成します- リクエストにより、
chargeオブジェクトを含むcharge.createイベントが生成されます - オブジェクトは
2019-05-29ではなく、Charge API のバージョン2017-11-02に従ってシリアライズされます - アカウントをバージョン
2019-05-29に更新した後も、イベントはバージョン2017-11-02に従ってシリアライズされたままです
要件とベストプラクティス
技術要件
- URLは 必ず HTTPSを使用してください
- URLは 必ず 有効なSSL証明書を使用してください(自己署名証明書はサポートされていません)
- SSL Labs でSSL証明書を検証してください
- 無料のSSL証明書については、Let's Encrypt をご利用ください
セキュリティのベストプラクティス
推奨: 署名検証
Webhookの正当性を確保するための主要なセキュリティ対策として、署名検証の実装を強く推奨します。署名検証は、リクエストがOmiseから送信されており、改ざんされていないことを暗号学的に確認するものであり、不正なWebhookをエンドポイントへ送り込もうとする悪意ある攻撃者に対して最も強力な防御を提供します。実装の詳細については、エンドポイントの保護セクションをご参照ください。
代替手段: イベント検証
署名検証の実装が困難な場合は、代替手段としてイベント検証をご利用ください。Webhookイベント(例: charge.complete)を受信したら、リソースIDを使用して独立したGETリクエストを実行し、ステータスを確認します。この方法では不正なリクエストがエンドポイントに届くことを防ぐことはできませんが、受信後にイベントデータの正当性を検証することができます。
イベント検証ワークフローの例:
1. `charge.complete` Webhookを受信する
2. WebhookペイロードからCharge IDを取得する
3. `/charges/{CHARGE_ID}` にGETリクエストを実行する
4. チャージのステータスを独立して検証する
サポートされているイベント
カードイベント
| イベント名 | トリガー |
|---|---|
card.destroy |
カードが削除された |
card.update |
カードが更新された |
チャージイベント
| イベント名 | トリガー |
|---|---|
charge.capture |
チャージがキャプチャされた(手動キャプチャのみ) |
charge.complete |
チャージが正常に完了した(注意: 非3DSカード決済の完了はこのWebhookをトリガーしません) |
charge.create |
チャージが作成された |
charge.expire |
チャージが期限切れになった(バーコードAlipayのみ) |
charge.reverse |
チャージが取り消された(手動取消のみ) |
charge.update |
チャージが更新された |
顧客イベント
| イベント名 | トリガー |
|---|---|
customer.create |
顧客が作成された |
customer.destroy |
顧客が削除された |
customer.update |
顧客が更新された |
customer.update.card |
顧客を通じてカードが暗黙的に更新された |
異議申し立てイベント
| イベント名 | トリガー |
|---|---|
dispute.accept |
異議申し立てが受理された |
dispute.close |
異議申し立てがクローズされた |
dispute.create |
異議申し立てが開始された |
dispute.update |
異議申し立てが更新された |
受取人イベント
| イベント名 | トリガー |
|---|---|
recipient.activate |
受取人が有効化された |
recipient.create |
受取人が作成された |
recipient.deactivate |
受取人が無効化された |
recipient.destroy |
受取人が削除された |
recipient.update |
受取人が更新された |
recipient.verify |
受取人が確認された |
返金イベント
| イベント名 | トリガー |
|---|---|
refund.create |
返金が作成された |
スケジュールイベント
| イベント名 | トリガー |
|---|---|
schedule.create |
スケジュールが作成された |
schedule.destroy |
スケジュールが削除された |
schedule.expire |
スケジュールが期限切れになった |
schedule.expiring |
スケジュールはまもなく期限切れになります |
schedule.suspend |
スケジュールが停止された |
振込イベント
| イベント名 | トリガー |
|---|---|
transfer.create |
振込が作成された |
transfer.destroy |
振込が削除された |
transfer.fail |
振込が失敗とマークされた |
transfer.pay |
振込が支払済みとマークされた |
transfer.send |
振込が送金済みとマークされた |
transfer.update |
振込が更新された |
ダイナミックWebhook
すべてのOmiseアカウントには、設定可能な静的Webhookエンドポイントが1つ含まれています。デフォルトでは、すべてのイベントの通知がこのエンドポイントに送信されます。
ダイナミックWebhook機能を使用すると、チャージ作成時に webhook_endpoints パラメーターを渡すことで、チャージごとにカスタムWebhookエンドポイントを指定できます。
動作
webhook_endpoints が指定されている場合:
- 対象チャージのすべてのイベントが、指定した
webhook_endpointsに送信されます - イベントは静的Webhookには 送信されません
webhook_endpoints が指定されていない場合:
- イベント通知はアカウントの静的Webhookに送信されます
エンドポイントの保護
OmiseはHMAC-SHA256アルゴリズムを使用してWebhookペイロードを暗号学的に署名します。アカウントにWebhookシークレットが設定されている場合、Webhookヘッダーにデジタル署名が含まれます。Webhookリクエストの正当性と完全性を確保するために、署名を検証してください。
Webhook署名ヘッダー
| ヘッダー | 説明 |
|---|---|
Omise-Signature |
OmiseによってHexエンコードされたHMAC署名。シークレットのローテーション中は、それぞれのシークレットに対応する2つの署名がカンマ区切りでヘッダーに含まれます。 |
Omise-Signature-Timestamp |
署名が生成されたUnixタイムスタンプ |
ヘッダーの例:
Omise-Signature: 072cc0d8b49d49ce7119857279b1e36a9efa25fadb468d0126628064d4062c83
Omise-Signature-Timestamp: 1758696391
// ローテーション中
Omise-Signature: 072cc0d8b49d49ce7119857279b1e36a9efa25fadb468d0126628064d4062c83,4bb1e14023f53d076e598245e5e16fd96a94d46072149c483815718eecf2a38d
Webhook署名の検証方法
ステップ1: 署名を取得する
Webhookリクエストから Omise-Signature および Omise-Signature-Timestamp ヘッダーを取得します。
ステップ2: 署名対象ペイロードを準備する
以下を連結してHMAC署名生成用のペイロードを構築します:
Omise-Signature-Timestampヘッダーの値- ドット (
.) - UTF-8エンコードされたWebhookリクエストのrawボディ
形式:
<TIMESTAMP>.<RAW_BODY>
ステップ3: Webhookシークレットをデコードする
WebhookシークレットはBase64エンコードされたHMACシークレットキーです。次のステップに進む前にデコードしてください。
ステップ4: 期待される署名を計算する
デコードしたシークレットを使用して署名対象ペイロードのHMAC-SHA256ダイジェストを計算し、結果を16進数文字列としてエンコードします。
ステップ5: 署名を比較する
Omise-Signatureヘッダー内のすべての署名を順番に処理します- 各署名を期待される署名と比較します
- いずれの署名も一致しない場合は、Webhookリクエストを拒否します
- 署名が一致した場合、直ちにWebhookの処理を進めることができます。または、リプレイアタックに対する追加のセキュリティとして、先にタイムスタンプを検証することもできます
重要なセキュリティ上の考慮事項:
- タイミング攻撃対策: タイミング攻撃を防ぐため、署名の比較には定数時間文字列比較アルゴリズムを使用してください
- リプレイアタック対策(任意): セキュリティを強化するため、現在のシステム時刻と受信したタイムスタンプの差を計算してタイムスタンプを検証してください。リプレイアタックを防ぐため、差が許容範囲(例: 5分)を超える場合はWebhookを拒否してください。タイムスタンプの検証は任意です。署名の一致のみでWebhookの正当性が確認されます。
実装例
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json({
verify: (req, res, buf) => {
req.rawBody = buf;
}
}));
function verifySignature({ req }) {
// ステップ1: 署名を取得する
const signatureHeader = req.headers['omise-signature'];
const timestampHeader = req.headers['omise-signature-timestamp'];
// ステップ2: 署名対象ペイロードを準備する
const rawBody = req.rawBody.toString('utf8');
const signedPayload = `${timestampHeader}.${rawBody}`;
// ステップ3: Webhookシークレットをデコードする
const secret = Buffer.from('<YOUR_WEBHOOK_SECRET>', 'base64');
// ステップ4: 期待される署名を計算する
const expectedBuffer = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest();
// ステップ5: ヘッダー内のすべての署名と比較する
const signatures = signatureHeader.split(',');
for (const sig of signatures) {
const sigBuffer = Buffer.from(sig, 'hex');
if (crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
// 任意: ここにタイムスタンプ検証を追加できます
return true;
}
}
return false;
}
app.post('/webhook', (req, res) => {
if (!verifySignature({ req })) {
return res.status(401).send('Invalid signature');
}
// 検証済みの場合、処理後に200を返す
res.status(200).send('OK');
});
Webhookシークレットの管理
Webhookシークレットの確認、生成、管理は、OmiseダッシュボードのWebhook設定(Webhooks Settings)から行えます。シークレットは環境ごとに独立しており、テストモードのシークレットはライブモードのシークレットとは別になっています。
⚠ 注意: Webhookシークレットは機密情報です。機密性の高い認証情報として取り扱ってください。クライアントサイドのコード、公開リポジトリへの公開、または安全でない環境での共有は絶対に行わないでください。
シークレットのローテーション
セキュリティコンプライアンス上の理由やシークレットが漏洩した場合など、シークレットを更新する必要がある場合、Omiseはダウンタイムなしのローテーション手順を提供しています:
- シークレットのローテーション: シークレットを「ロール」すると、新しいシークレットが即座に生成されます。サービスの中断を防ぐため、以前のシークレットは24時間有効のまま維持されます。
- デュアル署名: この24時間の移行期間中、Omiseは新旧両方のシークレットで各Webhookに署名します。
Omise-Signatureヘッダーにはカンマ区切りで2つの署名が含まれます。 - 手動失効: プライマリのアクティブシークレットを削除することはできません。ただし、移行期間をすぐに終了させたい場合は、24時間の期間中いつでも期限切れシークレットを手動で失効させることができます。
- 上限数: 同時にアクティブにできるシークレットは最大2つ(現行のものと期限切れになるもの)です。期限切れシークレットがまだアクティブな場合は、それが失効するか手動で失効させるまで、シークレットをロールできません。
テスト
ライブモードに切り替える前に、テストモードを使用して署名検証ロジックを検証することを推奨します。
- 安全なシミュレーション: テストモードでのシークレットのローテーションや失効は、本番のライブ環境には一切影響しません。
- ローテーションロジックの検証: テストモードで「シークレットローテーション」フローをテストすることを強く推奨します。これにより、本番環境でローテーションを実施する前に、デュアル署名の動作をコードが正しく処理できることを確認できます。
トラブルシューティング
よくある問題
Webhookが受信できない:
- エンドポイントURLが有効なSSL証明書を持つHTTPSであることを確認してください
- ファイアウォールが以下のOmise WebhookサーバーIPからの受信リクエストを許可していることを確認してください:
54.169.118.22752.74.199.17518.139.13.19
- エンドポイントがアクセス可能でリクエストに応答していることを確認してください
署名検証が失敗する:
- 使用している環境(テスト/ライブ)に対応した正しいWebhookシークレットを使用していることを確認してください
- 変更を加えていないrawリクエストボディを使用していることを確認してください
- WebhookシークレットのBase64デコードが正しく行われていることを確認してください
イベントが誤ったエンドポイントに送信される:
- チャージ作成時に
webhook_endpointsパラメーターが指定されているか確認してください - ダッシュボードで静的Webhookの設定を確認してください
よくある質問
複数の静的Webhookエンドポイントを設定できますか?
いいえ。各Omiseアカウントがサポートする静的Webhookエンドポイントは1つのみです。チャージごとに複数の送信先にイベントを送る必要がある場合は、ダイナミックWebhook機能を使用し、チャージ作成時に webhook_endpoints パラメーターへ複数のURLを渡してください。
すべてのイベントタイプのWebhookを自動的に受信できますか?
はい。デフォルトでは、静的Webhookエンドポイントはサポートされているすべてのイベントタイプの通知を受信します。現在、静的エンドポイントに対してアカウントレベルで特定のイベントタイプをフィルタリングするオプションはありません。
エンドポイントが返すべきHTTPレスポンスコードは何ですか?
Webhookを正常に受信・処理した後、エンドポイントは 200 OK レスポンスを返す必要があります。2xx以外のステータスコードを返すと、Omiseは配信失敗として扱う場合があります。
Omiseは失敗したWebhook配信を再試行しますか?
Omiseは現在、失敗した配信の自動再試行を保証していません。イベントを見逃さないよう、フォールバックとしてイベント検証ワークフローを実装するか、Events APIで見逃したイベントをポーリングしてください。
完了した非3DSカード決済のWebhookを受信しなかったのはなぜですか?
charge.complete イベントは非3DSカード決済ではトリガーされません。3DS認証フローまたはその他の非同期決済手段を経たチャージのみが charge.complete Webhookを生成します。
テストモードとライブモードで同じWebhookエンドポイントを使用できますか?
はい、ただし推奨しません。テストモードとライブモードはそれぞれ異なるWebhookシークレットを使用するため、エンドポイントが両方を処理する必要があります。環境ごとに別々のエンドポイントを維持する方が、より安全で管理しやすいです。
ダウンタイムなしでWebhookシークレットのローテーションを行うにはどうすればよいですか?
Webhooks Settingsの「ロール」オプションを使用してください。24時間の移行期間中、Omiseは Omise-Signature ヘッダーに新旧両方の署名を送信します。旧シークレットを失効させる前に、いずれの署名も受け入れるよう検証ロジックを更新してください。詳細はシークレットのローテーションをご参照ください。
Webhookシークレットが漏洩した場合はどうすればよいですか?
直ちにWebhooks Settingsでシークレットをロールして新しいシークレットを生成し、移行期間を即座に終了させるために期限切れシークレットを手動で失効させてください。できるだけ早くアプリケーションを新しいシークレットに更新してください。