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 に設定されている場合:

  1. -H "Omise-Version: 2019-05-29" を指定した curl でチャージを作成します
  2. リクエストにより、charge オブジェクトを含む charge.create イベントが生成されます
  3. オブジェクトは 2019-05-29 ではなく、Charge API のバージョン 2017-11-02 に従ってシリアライズされます
  4. アカウントをバージョン 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署名生成用のペイロードを構築します:

  1. Omise-Signature-Timestamp ヘッダーの値
  2. ドット (.)
  3. UTF-8エンコードされたWebhookリクエストのrawボディ

形式:

<TIMESTAMP>.<RAW_BODY>

ステップ3: Webhookシークレットをデコードする

WebhookシークレットはBase64エンコードされたHMACシークレットキーです。次のステップに進む前にデコードしてください。

ステップ4: 期待される署名を計算する

デコードしたシークレットを使用して署名対象ペイロードのHMAC-SHA256ダイジェストを計算し、結果を16進数文字列としてエンコードします。

ステップ5: 署名を比較する

  1. Omise-Signature ヘッダー内のすべての署名を順番に処理します
  2. 各署名を期待される署名と比較します
  3. いずれの署名も一致しない場合は、Webhookリクエストを拒否します
  4. 署名が一致した場合、直ちに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.227
    • 52.74.199.175
    • 18.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でシークレットをロールして新しいシークレットを生成し、移行期間を即座に終了させるために期限切れシークレットを手動で失効させてください。できるだけ早くアプリケーションを新しいシークレットに更新してください。

追加リソース

Omiseは、お客様のウェブサイト全般における利便性を向上するためにクッキーを利用し、お客様のアクセス、閲覧履歴に関する情報を収集します。 当社のウェブサイトを閲覧し続けることにより、お客様は当社のプライバシーポリシーに同意することとします。 詳細はこちら