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

ウェブフック のテスト

ウェブフック 配信のテスト、ウェブフック ハンドラーの実装、ウェブフック イベント のデバッグ、および Omise 統合の自動ウェブフック テストの設定に関する完全なガイド。

概要

ウェブフック は、支払いイベント でリアルタイムにアプリケーション に通知する HTTP コールバック です。ウェブフック を正しくテストすることで、アプリケーション が支払いイベント を正しく処理し、重複イベント を安全に処理し、障害 から正常に回復することを確認できます。

ウェブフック をテストする理由

  • イベント処理: すべての支払いイベント が正しく処理されることを確認
  • べき等性: 重複 ウェブフック 配信 を安全に処理
  • セキュリティ: ウェブフック シグネチャ を検証して偽造 を防止
  • 信頼性: 配信 障害 とネットワーク 問題 を処理
  • パフォーマンス: タイムアウト なしに非同期 でウェブフック を処理
  • デバッグ: 統合 問題 をすばやく特定して修正

ウェブフック テスト戦略

  1. ローカルテスト: ngrok を使用してローカルマシン でウェブフック をテスト
  2. モックテスト: クイックテスト用のウェブフック テストサービスを使用
  3. 自動テスト: ウェブフック ハンドラー 用のテストを記述
  4. 統合テスト: エンド ツー エンド のウェブフック フロー をテスト
  5. 本番環境監視: ウェブフック 配信 と処理 を監視

ウェブフック テストのセットアップ

ウェブフック エンドポイント を構成

まず、Omise ダッシュボード でウェブフック エンドポイント を構成してください:

  1. Settings > Webhooks に移動
  2. Add Webhook Endpoint をクリック
  3. ウェブフック URL を入力
  4. 受け取るイベント を選択
  5. 構成 を保存

ローカル開発 では、ngrok を使用して公開 URL を作成してください。

ウェブフック イベント を理解

Omise は次のイベント 用のウェブフック を送信します:

イベント説明
charge.createチャージ が作成されました
charge.completeチャージ が正常に完了しました
charge.expireチャージ は支払い なしで期限切れ になりました
refund.create払戻 が作成されました
transfer.create転送 が作成されました
transfer.pay転送 が支払い されました
customer.create顧客 が作成されました
customer.update顧客 が更新されました
customer.destroy顧客 が削除されました
card.createカード が作成されました
card.updateカード が更新されました
card.destroyカード が削除されました
dispute.create異議 が作成されました
dispute.update異議 が更新されました

ngrok を使用したテスト

ngrok をインストール

ngrok は localhost へのセキュアなトンネル を作成し、Omise がウェブフック を開発マシン に送信できるようにします。

インストール:

# macOS (Homebrew)
brew install ngrok

# Windows (Chocolatey)
choco install ngrok

# Linux (直接ダウンロード)
wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
unzip ngrok-stable-linux-amd64.zip
sudo mv ngrok /usr/local/bin/

# インストール を確認
ngrok version

認証:

# https://dashboard.ngrok.com/signup でサインアップ
# https://dashboard.ngrok.com/get-started/your-authtoken からトークン を取得

ngrok config add-authtoken YOUR_AUTH_TOKEN

ウェブフック テスト用に ngrok を使用

ngrok トンネル を開始:

# ローカルポート 3000 へ転送
ngrok http 3000

# カスタムサブドメイン 付き (有料プラン)
ngrok http -subdomain=myapp 3000

# カスタム地域 付き
ngrok http -region=ap 3000

ngrok 出力:

Session Status                online
Account your@email.com
Version 3.0.0
Region Asia Pacific (ap)
Web Interface http://127.0.0.1:4040
Forwarding https://abc123.ap.ngrok.io -> http://localhost:3000

Omise ダッシュボード で転送 URL を使用:

Webhook URL: https://abc123.ap.ngrok.io/webhooks

Webhook.site によるテスト

Webhook.site を使用

Webhook.site はコード を書かずにウェブフック をテストするための一時 URL を提供します。

ステップ:

  1. https://webhook.site に移動
  2. ユニークな URL をコピー (例: https://webhook.site/abc-123)
  3. この URL を Omise ダッシュボード ウェブフック に追加
  4. Omise でテスト イベント をトリガー
  5. webhook.site でリアルタイム にリクエスト を表示

機能:

  • リクエスト ヘッダー、ボディ、クエリ パラメータ を表示
  • レスポンス ステータス とボディ を検査
  • デバッグ 用にレスポンス を編集してカスタマイズ
  • リクエスト データ をエクスポート
  • ウェブフック URL をチーム と共有

Postman によるテスト

Postman ウェブフック テスト用にセットアップ

Postman はテスト ウェブフック リクエスト をローカル エンドポイント に送信できます。

ウェブフック テスト コレクション を作成:

[Postman コレクション JSON 例]

Postman でシグネチャ を生成

事前リクエスト スクリプト:

// Postman 事前リクエスト スクリプト
const CryptoJS = require('crypto-js');

// 環境変数 からシークレット キー を取得
const secretKey = pm.environment.get('OMISE_SECRET_KEY');

// リクエスト ボディ を取得
const payload = pm.request.body.raw;

// HMAC シグネチャ を生成
const signature = CryptoJS.HmacSHA256(payload, secretKey).toString();

// シグネチャ ヘッダー を設定
pm.environment.set('signature', signature);
console.log('Generated signature:', signature);

自動ウェブフック テスト

Jest (JavaScript/Node.js) でのテスト

// webhook.test.js - ウェブフック ハンドラー 用の Jest テスト
const request = require('supertest');
const crypto = require('crypto');
const app = require('./app'); // Express アプリ

describe('Webhook Handler', () => {
const SECRET_KEY = 'skey_test_xxxxxxxxxx';

function generateSignature(payload) {
return crypto
.createHmac('sha256', SECRET_KEY)
.update(JSON.stringify(payload))
.digest('hex');
}

describe('POST /webhooks', () => {
test('should accept valid webhook with correct signature', async () => {
const payload = {
key: 'charge.complete',
data: {
id: 'chrg_test_123',
amount: 100000,
currency: 'THB',
status: 'successful'
}
};

const signature = generateSignature(payload);

const response = await request(app)
.post('/webhooks')
.set('X-Omise-Signature', signature)
.send(payload);

expect(response.status).toBe(200);
expect(response.body).toEqual({ received: true });
});

test('should reject webhook with invalid signature', async () => {
const payload = {
key: 'charge.complete',
data: {
id: 'chrg_test_123',
amount: 100000,
currency: 'THB',
status: 'successful'
}
};

const response = await request(app)
.post('/webhooks')
.set('X-Omise-Signature', 'invalid_signature')
.send(payload);

expect(response.status).toBe(401);
expect(response.body).toHaveProperty('error');
});

test('should handle charge.complete event', async () => {
const payload = {
key: 'charge.complete',
data: {
id: 'chrg_test_123',
amount: 100000,
currency: 'THB',
status: 'successful'
}
};

const signature = generateSignature(payload);

const response = await request(app)
.post('/webhooks')
.set('X-Omise-Signature', signature)
.send(payload);

expect(response.status).toBe(200);
// ウェブフック が処理されたことを確認
});

test('should handle duplicate webhooks idempotently', async () => {
const payload = {
key: 'charge.complete',
data: {
id: 'chrg_test_125',
amount: 100000,
currency: 'THB',
status: 'successful'
}
};

const signature = generateSignature(payload);

// 同じウェブフック を2回送信
const response1 = await request(app)
.post('/webhooks')
.set('X-Omise-Signature', signature)
.send(payload);

const response2 = await request(app)
.post('/webhooks')
.set('X-Omise-Signature', signature)
.send(payload);

expect(response1.status).toBe(200);
expect(response2.status).toBe(200);
// イベント が1回だけ処理されたことを確認
});
});
});

Pytest (Python) でのテスト

# test_webhooks.py - ウェブフック ハンドラー 用の Pytest テスト
import pytest
import json
import hmac
import hashlib
from flask import Flask
from app import app

@pytest.fixture
def client():
"""テスト クライアント を作成"""
app.config['TESTING'] = True
with app.test_client() as client:
yield client

@pytest.fixture
def secret_key():
"""テスト シークレット キー"""
return 'skey_test_xxxxxxxxxx'

def generate_signature(payload, secret_key):
"""ウェブフック 用の HMAC シグネチャ を生成"""
payload_str = json.dumps(payload)
signature = hmac.new(
secret_key.encode('utf-8'),
payload_str.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature

class TestWebhookHandler:
"""ウェブフック ハンドラー をテスト"""

def test_valid_webhook_with_correct_signature(self, client, secret_key):
"""正しいシグネチャ で有効なウェブフック を受け入れるべき"""
payload = {
'key': 'charge.complete',
'data': {
'id': 'chrg_test_123',
'amount': 100000,
'currency': 'THB',
'status': 'successful'
}
}

signature = generate_signature(payload, secret_key)

response = client.post(
'/webhooks',
data=json.dumps(payload),
headers={
'Content-Type': 'application/json',
'X-Omise-Signature': signature
}
)

assert response.status_code == 200
assert response.json['received'] == True

def test_handle_duplicate_webhooks(self, client, secret_key):
"""べき等 的に重複 ウェブフック を処理するべき"""
payload = {
'key': 'charge.complete',
'data': {
'id': 'chrg_test_125',
'amount': 100000,
'currency': 'THB',
'status': 'successful'
}
}

signature = generate_signature(payload, secret_key)

# 同じウェブフック を2回送信
response1 = client.post(
'/webhooks',
data=json.dumps(payload),
headers={
'Content-Type': 'application/json',
'X-Omise-Signature': signature
}
)

response2 = client.post(
'/webhooks',
data=json.dumps(payload),
headers={
'Content-Type': 'application/json',
'X-Omise-Signature': signature
}
)

assert response1.status_code == 200
assert response2.status_code == 200
# イベント が1回だけ処理されたことを確認

ベストプラクティス

1. ウェブフック シグネチャ を検証

スプーフィング を防ぐためにウェブフック シグネチャ を常に検証:

<?php
// PHP - ウェブフック シグネチャ 検証
function verifyWebhookSignature($payload, $signature, $secretKey) {
$expectedSignature = hash_hmac('sha256', $payload, $secretKey);

// タイミング セーフ な比較を使用
return hash_equals($expectedSignature, $signature);
}

// 使用法
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_OMISE_SIGNATURE'] ?? '';

if (!verifyWebhookSignature($payload, $signature, $secretKey)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}

// ウェブフック を処理
$event = json_decode($payload, true);
processWebhook($event);
?>

2. ウェブフック をべき等 に処理

重複配信 を安全に処理:

// Go - べき等 なウェブフック 処理
func handleWebhook(w http.ResponseWriter, r *http.Request, db *sql.DB) {
var event WebhookEvent
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}

// イベント ID を抽出
var data map[string]interface{}
json.Unmarshal(event.Data, &data)
eventID := data["id"].(string)

// すでに処理済みか どうかを確認
var exists bool
err := db.QueryRow(
"SELECT EXISTS(SELECT 1 FROM processed_webhooks WHERE event_id = $1)",
eventID,
).Scan(&exists)

if exists {
// すでに処理済み、成功 を返す
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
return
}

// ウェブフック を処理
if err := processWebhookEvent(event); err != nil {
http.Error(w, "Processing error", http.StatusInternalServerError)
return
}

// 処理済み としてマーク
_, err = db.Exec(
"INSERT INTO processed_webhooks (event_id, event_key, processed_at) VALUES ($1, $2, $3)",
eventID, event.Key, time.Now(),
)

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}

3. 迅速に応答

30秒以内 にウェブフック に応答:

# Python - 非同期 ウェブフック 処理
from flask import Flask, request, jsonify
import threading
import queue

app = Flask(__name__)
webhook_queue = queue.Queue()

def process_webhooks_async():
"""ウェブフック を処理するためのバック グラウンド ワーカー"""
while True:
try:
event = webhook_queue.get()

# イベント を処理
process_webhook_event(event)

webhook_queue.task_done()
except Exception as e:
print(f'Error processing webhook: {e}')

# バック グラウンド ワーカー を開始
worker_thread = threading.Thread(target=process_webhooks_async, daemon=True)
worker_thread.start()

@app.route('/webhooks', methods=['POST'])
def handle_webhook():
# シグネチャ を検証
if not verify_signature(request):
return jsonify({'error': 'Invalid signature'}), 401

# 処理 のためにキュー に追加
event = request.json
webhook_queue.put(event)

# 迅速に応答
return jsonify({'received': True}), 200

4. ウェブフック イベント をログ

デバッグ 用に包括的なログを実装:

// JavaScript - ウェブフック ログ
const winston = require('winston');

const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'webhooks-error.log', level: 'error' }),
new winston.transports.File({ filename: 'webhooks-combined.log' })
]
});

app.post('/webhooks/omise', (req, res) => {
const event = req.body;
const signature = req.headers['x-omise-signature'];

// 受信 したウェブフック をログ
logger.info('Webhook received', {
event_key: event.key,
event_id: event.data?.id,
timestamp: new Date().toISOString()
});

try {
// シグネチャ を検証
if (!verifySignature(req.body, signature)) {
logger.error('Invalid webhook signature', {
event_key: event.key,
signature: signature
});
return res.status(401).json({ error: 'Invalid signature' });
}

// ウェブフック を処理
processWebhook(event);

logger.info('Webhook processed successfully', {
event_key: event.key,
event_id: event.data?.id
});

res.json({ received: true });
} catch (error) {
logger.error('Webhook processing failed', {
event_key: event.key,
error: error.message
});
res.status(500).json({ error: 'Processing failed' });
}
});

5. ウェブフック の状態を監視

ウェブフック 配信と処理を追跡:

# Ruby - ウェブフック の状態監視
class WebhookMonitor
def self.record_webhook(event_key, status, duration_ms)
WebhookMetric.create(
event_key: event_key,
status: status,
duration_ms: duration_ms,
timestamp: Time.now
)
end

def self.health_check
recent_webhooks = WebhookMetric
.where('timestamp > ?', 1.hour.ago)

total = recent_webhooks.count
successful = recent_webhooks.where(status: 'success').count
failed = recent_webhooks.where(status: 'error').count

success_rate = total > 0 ? (successful.to_f / total * 100).round(2) : 0
avg_duration = recent_webhooks.average(:duration_ms)&.round(2) || 0

{
period: '1 hour',
total: total,
successful: successful,
failed: failed,
success_rate: success_rate,
avg_duration_ms: avg_duration,
status: success_rate >= 95 ? 'healthy' : 'degraded'
}
end
end

トラブルシューティング

一般的な問題

問題: ウェブフック が受信されていません

考えられる原因:

  • Omise ダッシュボード でウェブフック URL が構成されていません
  • ファイアウォール がリクエスト をブロックしています
  • ngrok トンネル が実行されていません
  • サーバー が正しいポート でリッスン していません

解決策:

  1. Omise ダッシュボード でウェブフック URL を確認
  2. サーバー ログ で受信 リクエスト を確認
  3. 最初に webhook.site でテスト
  4. ngrok が実行されていることを確認: curl http://localhost:4040/api/tunnels

問題: シグネチャ 検証に失敗

考えられる原因:

  • 間違ったシークレット キー を使用している
  • 正しくないペイロード 形式と比較
  • エンコーディング の問題

解決策:

// シグネチャ 検証をデバッグ
const receivedSignature = req.headers['x-omise-signature'];
const payload = JSON.stringify(req.body);
const secretKey = process.env.OMISE_SECRET_KEY;

const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(payload)
.digest('hex');

console.log('Received:', receivedSignature);
console.log('Expected:', expectedSignature);
console.log('Match:', receivedSignature === expectedSignature);

FAQ

Omise はウェブフック レスポンス をどのくらい待ちますか?

Omise はエンドポイント がレスポンス するまで30秒待機します。30秒以内 にレスポンス を受け取らない場合、ウェブフック は失敗 としてマークされ、再試行 されます。

失敗したウェブフック を再試行するにはどうすればよいですか?

Omise ダッシュボード からウェブフック を手動で再トリガー できます。設定 > ウェブフック に移動し、イベント を見つけて、「再送信」をクリックしてください。

ウェブフック シグネチャ を検証する必要がありますか?

はい、偽造 を防ぐためにウェブフック シグネチャ を常に検証してください。Omise はシークレット キー を使用 して HMAC-SHA256 ですべてのウェブフック に署名します。

テストモード でウェブフック をテストできますか?

はい、テストモード は本番環境 と同じようにウェブフック を送信 します。Omise ダッシュボード でテストモード ウェブフック エンドポイント を構成してください。

関連リソース

次のステップ

  1. ローカル ウェブフック テスト 用に ngrok をセットアップ
  2. ウェブフック ハンドラー でシグネチャ 検証を実装
  3. 重複処理 を防ぐためにべき等 処理を追加
  4. ウェブフック ハンドラー 用の自動テスト を記述
  5. ウェブフック を非同期 で処理してパフォーマンス を改善
  6. デバッグ 用に包括的なログ を追加
  7. ウェブフック の状態 を監視するセットアップを実施
  8. アプリケーション が処理するすべてのウェブフック イベント をテスト

デプロイ 準備ができましたか? 本番環境 チェックリストを確認してください。

テストデータが必要ですか? テストカードとデータを参照してください。