Webhook(ウェブフック)
このページで扱うトピック
Webhooks (ウェブフック)
概要
Webhooks(ウェブフック)は、特定のイベントが加盟店のアカウントで発生した際に、Omiseのサーバーから加盟店が指定したHTTPSエンドポイントに送信されるHTTP POSTリクエストです。各リクエストには、そのイベントを引き起こしたアクションに関するデータペイロードを含むイベントオブジェクトが含まれます。
Webhooksを使用することで、加盟店のアカウントで重要なイベントが発生した際に、非同期かつリアルタイムで通知を受け取ることが可能になります。APIを繰り返しポーリングしてステータスの変更を確認する代わりに、イベントが発生した瞬間に、通知が直接、加盟店のサーバーへと送信されます。
主なメリット:
- 効率化:継続的なポーリングが不要になり、不要なAPIコールやサーバー負荷を削減できます
- リアルタイムな情報:課金の完了、返金、異議申し立てなどのイベント発生時に即座に通知を受け取れます
- 自動化:イベント発生と同時に、注文ステータスの更新、顧客へのメール送信、会計処理の自動化など、加盟店のアプリケーション内でワークフローを自動的にトリガーできます。
- 信頼性:アプリケーションが更新をチェックしていない状況でも、重要なイベントの通知を確実に受け取ることができます
設定
テスト環境と本番環境でウェブフックエンドポイントを別々に設定してください。
イベントデータの確認
ウェブフックのイベントデータには、以下の方法でアクセスできます。
- ダッシュボードの「イベント」セクションにアクセス。右側のパネルでアカウントアイコンをクリックし、「イベント」を選択
- イベントAPI
シリアライズ
POSTリクエスト内のイベントオブジェクト(またはイベントAPIを通じて返されるもの)は、トリガーとなるリクエストで送信されたOmise-Versionヘッダーの値に関係なく、イベントの時点でのデフォルトAPIバージョンに従って常にシリアライズされます。
例
アカウントがバージョン2017-11-02に設定されている場合
curlを使って-H "Omise-Version: 2019-05-29"ヘッダーでチャージを作成- このリクエストは
charge.createイベントを生成し、その中にchargeオブジェクトが埋め込まれます - オブジェクトはCharge APIのバージョン2017-11-02に従ってシリアライズされ、バージョン
2019-05-29には従いません - たとえアカウントをバージョン
2019-05-29に更新しても、イベントはバージョン2017-11-02に従ってシリアライズされたままとなります
要件とベストプラクティス
技術的要件
- URLはHTTPSを使用する必要があります
- URLは有効なSSL証明書を使用する必要があります(自己署名証明書はサポートされていません)
- SSL証明書の検証には、SSL Labsを使用してください
- 無料のSSL証明書を利用するには、Let's Encryptをご覧ください
セキュリティのベストプラクティス
推奨:署名検証
ウェブフックの真正性を確認するための主なセキュリティ対策として、署名検証の実装を強く推奨します。署名検証は、リクエストがOmiseから発信され、改ざんされていないことを暗号的に確認するもので、悪意のある者が不正なウェブフックを加盟店のエンドポイントに送信することを防ぐための強力な保護手段です。実装の詳細については、エンドポイント保護セクションを参照してください。
代替手段:イベント検証
署名検証が実装できない場合は、代替手段としてイベント検証を使用してください。ウェブフックイベント(例:charge.complete)を受信した際には、リソースIDを使用して独立したGETリクエストを実行し、ステータスを確認します。ただし、この方法は不正なリクエストをエンドポイントが受け取ることを防ぐものではなく、受信した後にイベントデータの真正性を確認するための手段です。
イベント検証のワークフロー例:
1. Receive `charge.complete` webhook
2. Extract Charge ID from the webhook payload
3. Perform GET request to `/charges/chrg_test_no1t4tnemucod0e51mo`
4. Verify the charge status independently
サポートされているイベント
クレジットカードに関するイベント
| イベント名 | イベント名 |
|---|---|
card.destroy |
カードが削除された |
card.update |
カードが更新された |
課金に関するイベント
| イベント名 | イベント名 |
|---|---|
charge.capture |
課金が実売上化された(手動での実売上化設定の場合) |
charge.complete |
課金が完了した(注:3DS非対応カードの課金完了時には、このウェブフックをトリガーしません) |
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 |
チャージバックが更新した |
リンクに関するイベント
| イベント名 | イベント名 |
|---|---|
link.create |
リンクが作成された |
連携口座に関するイベント
| イベント名 | イベント名 |
|---|---|
linked_account.create |
連携口座が作成された |
linked_account.complete |
顧客が連携口座を登録した |
受取口座に関するイベント
| イベント名 | イベント名 |
|---|---|
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 |
振込が更新された |
動的ウェブフック
すべてのOmiseアカウントには、単一の設定可能な静的ウェブフックエンドポイントが含まれています。デフォルトでは、すべてのイベントに関する通知はこのエンドポイントに送信されます。
動的ウェブフック機能を使用すると、チャージごとにカスタムのウェブフックエンドポイントを指定することが可能です。これは、チャージを作成する際にwebhook_endpointsパラメータを渡すことで実現できます。
挙動
webhook_endpointsが指定されている場合:
- 該当のチャージに関するすべてのイベントは、指定された
webhook_endpointsに送信されます。 - 静的ウェブフックには送信されません。
webhook_endpointsが指定されていない場合:
- イベント通知は、アカウントに設定されている静的ウェブフックに送信されます。
エンドポイントの保護
Omiseは、HMAC-SHA256アルゴリズムを使用して、ウェブフックのペイロードに暗号署名を行います。アカウントにウェブフックシークレットが設定されている場合、デジタル署名がウェブフックのヘッダーに含まれます。この署名を検証することで、ウェブフックリクエストの正当性(認証)および改ざんされていないこと(完全性)を確認できます。
ウェブフック署名ヘッダー
| ヘッダー名 | 説明 |
|---|---|
Omise-Signature |
Omiseによって生成されたHMAC署名(16進エンコード形式)。シークレットのローテーション中は、ヘッダーには2つの署名がカンマ区切りで含まれ、それぞれが異なるシークレットに対応します。 |
Omise-Signature-Timestamp |
署名が生成された時刻を表すUnixタイムスタンプです。 |
ヘッダーの例:
Omise-Signature: 072cc0d8b49d49ce7119857279b1e36a9efa25fadb468d0126628064d4062c83
Omise-Signature-Timestamp: 1758696391
// During rotation
Omise-Signature: 072cc0d8b49d49ce7119857279b1e36a9efa25fadb468d0126628064d4062c83,4bb1e14023f53d076e598245e5e16fd96a94d46072149c483815718eecf2a38d
ウェブフック署名を検証する方法
ステップ 1:署名の取得
ウェブフックリクエストのヘッダーから、Omise-Signatureと Omise-Signature-Timestampを抽出します。
ステップ 2:署名対象のペイロードを準備
HMAC署名の生成に使用するペイロードを、次の要素を連結して構築します。
Omise-Signature-Timestampヘッダーの値- ドット(.)
- UTF-8エンコードされた生のウェブフックリクエストボディ
フォーマット:
<TIMESTAMP>.<RAW_BODY>
ステップ 3:ウェブフックシークレットをデコードする
ウェブフックシークレットは、Base64エンコードされたHMACシークレットキーです。次のステップに進む前に、これをデコードしてください。
ステップ 4:期待される署名を計算する
デコードしたシークレットを使用して署名対象ペイロードの HMAC-SHA256ダイジェストを計算し、その結果を16進数文字列としてエンコードします。
ステップ 5:署名を比較する
Omise-Signatureヘッダーに含まれるすべての署名を順に確認します。- 各署名を、ステップ4で生成した期待される署名と比較します。
- どの署名も一致しない場合は、ウェブフックリクエストを拒否します。
- 署名が一致する場合は、すぐにウェブフックの処理を進めることができます。また、リプレイ攻撃対策として、署名のタイムスタンプの検証を追加で行うことも推奨されます。
重要なセキュリティ上の考慮事項:
- 署名を比較する際は、タイミング攻撃を防ぐために、一定時間で処理される文字列比較アルゴリズムを使用してください。
- リプレイ攻撃対策(任意):セキュリティをさらに強化するには、次の方法でリプレイ攻撃を防止できます。現在のシステム時刻と、
Omise-Signature-Timestampに含まれるタイムスタンプとの差分を計算します。その差が許容範囲(例:5分)を超えている場合、ウェブフックリクエストを拒否してください。なお、タイムスタンプの検証は任意です。署名の照合に成功した時点で、ウェブフックの正当性は保証されています。
実装例
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 }) {
// Step 1: Retrieve the signature(s)
const signatureHeader = req.headers['omise-signature'];
const timestampHeader = req.headers['omise-signature-timestamp'];
// Step 2: Prepare the signed payload
const rawBody = req.rawBody.toString('utf8');
const signedPayload = `${timestampHeader}.${rawBody}`;
// Step 3: Decode the webhook secret
const secret = Buffer.from('<YOUR_WEBHOOK_SECRET>', 'base64');
// Step 4: Compute the expected signature
const expectedBuffer = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest();
// Step 5: Compare against all signatures in the header
const signatures = signatureHeader.split(',');
for (const sig of signatures) {
const sigBuffer = Buffer.from(sig, 'hex');
if (crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
// Optional: Add timestamp validation here
return true;
}
}
return false;
}
app.post('/webhook', (req, res) => {
if (!verifySignature(req)) {
res.status(401).send('Invalid signature')
}
// If verified, return 200 after processing webhook
res.status(200).send('OK');
});
ウェブフックシークレットの管理
Omiseのダッシュボードのウェブフック設定セクションで、ウェブフックシークレットの確認、生成、管理が行えます。シークレットは環境ごとに個別に管理されており、テスト環境のシークレットと本番環境のシークレットはそれぞれ異なるものです。
【注意】ウェブフックシークレットは機密情報です。認証情報と同様に厳重に扱います。クライアントサイドのコードや公開リポジトリに含めたり、安全でない環境で共有したりしないでください。
シークレットのローテーション
セキュリティ要件への対応や、シークレットが漏洩した可能性がある場合など、シークレットを更新する必要がある際、Omiseではサービスを停止することなくシークレットをローテーションできます。
- シークレットのローテーション:シークレットを「ロール(Roll)」すると、新しいシークレットが即座に生成されます。サービスの中断を防ぐため、以前のシークレットは24時間有効なまま保持されます。
- デュアル署名:この24時間の移行期間中、Omiseはすべてのウェブフックに対して新旧両方のシークレットで署名を行います。Omise-Signature ヘッダーには、カンマで区切られた2つの署名が含まれます。
- 手動での失効:現在有効なプライマリシークレットを削除することはできません。ただし、24時間の有効期限付きのシークレットについては、移行期間中いつでも手動で失効させることができ、その場合は移行期間が即座に終了します。
- シークレット数の上限:同時に有効化できるシークレットは最大2つです(現在のシークレット1つ+失効予定のシークレット1つ)。失効予定のシークレットがまだ有効な場合は、その期限が切れるか、手動で失効させるまで、次のローテーションを実行することはできません。
テスト
本番環境に切り替える前に、テスト環境で署名検証ロジックを検証することを推奨します。
- 安全な環境でのシミュレーション:テスト環境でシークレットをローテーションまたは失効させても、本番環境には一切影響しません。
- ローテーションロジックの検証:本番環境でローテーションを実施する前に、テスト環境で「シークレットローテーション」のフローを検証することを強く推奨します。デュアル署名の挙動をコードが正しく処理できているどうかを事前にご確認ください。
トラブルシューティング
よくあるトラブル
ウェブフックが届かない:
- エンドポイントのURLがHTTPSで、有効なSSL証明書を使用していることを確認してください。
- ファイアウォールがOmiseサーバーからの受信リクエストを許可しているか確認してください。
- エンドポイントがアクセス可能で、リクエストに適切に応答しているかを確認してください。
署名の検証に失敗する:
- ご利用の環境(テスト環境と本番環境)に適したウェブフックシークレットを使用していることを確認してください。
- リクエストボディの生データ(raw body)を、そのまま変更せずに使用しているかを確認してください。
- ウェブフックシークレットが正しくBase64デコードされていることを確認してください。
イベントが誤ったエンドポイントに送信される:
- 課金作成時に
webhook_endpointsパラメータが指定されているか確認してください。 - ダッシュボードで設定されている静的ウェブフックの設定を見直してください。