Webhookのテスト
Webhook配信のテスト、Webhookハンドラーの実装、Webhookイベントのデバッグ、およびOmise統合の自動Webhookテストの設定に関する完全なガイド。
インタラクティブWebhookテスター
このツールを使用して、Webhookイベントを探索し、ペイロード例を表示し、エンドポイントをテストできます:
インタラクティブWebhookテスター
Webhookイベントを探索し、エンドポイントをテスト
charge.complete{
"object": "event",
"id": "evnt_test_5h2m123lxlx4z7yh9a2",
"livemode": false,
"location": "/events/evnt_test_5h2m123lxlx4z7yh9a2",
"webhook_deliveries": [],
"data": {
"object": "charge",
"id": "chrg_test_5h2m123abc456def",
"amount": 100000,
"currency": "thb",
"description": "Test charge",
"status": "successful",
"authorized": true,
"paid": true,
"captured": true,
"capture": true,
"refunded": 0,
"reversed": false,
"voided": false,
"expired": false,
"disputable": true,
"capturable": false,
"reversible": false,
"transaction": "trxn_test_5h2m123abc456def",
"source_of_fund": "card",
"failure_code": null,
"failure_message": null,
"card": {
"object": "card",
"id": "card_test_5h2m123abc456def",
"livemode": false,
"brand": "Visa",
"last_digits": "4242",
"name": "Test User",
"expiration_month": 12,
"expiration_year": 2025,
"fingerprint": "FpEYKqwFa3znwjpHlEHjHg==",
"security_code_check": true
},
"created_at": "2026-03-09T09:55:36.832Z"
},
"key": "charge.complete",
"created_at": "2026-03-09T09:55:36.833Z"
}概要
Webhookは、支払いイベントでリアルタイムにアプリケーションに通知するHTTPコールバックです。Webhookを正しくテストすることで、アプリケーションが支払いイベントを正しく処理し、重複イベントを安全に処理し、障害から正常に回復することを確認できます。
Webhookをテストする理由
- イベント処理: すべての支払いイベントが正しく処理されることを確認
- べき等性: 重複Webhook配信を安全に処理
- セキュリティ: Webhookシグネチャを検証して偽造を防止
- 信頼性: 配信障害とネットワーク問題を処理
- パフォーマンス: タイムアウトなしに非同期でWebhookを処理
- デバッグ: 統合問題をすばやく特定して修正
Webhookテスト戦略
- ローカルテスト: ngrokを使用してローカルマシンでWebhookをテスト
- モックテ スト: クイックテスト用のWebhookテストサービスを使用
- 自動テスト: Webhookハンドラー用のテストを記述
- 統合テスト: エンドツーエンドのWebhookフローをテスト
- 本番環境監視: Webhook配信と処理を監視
Webhookテストのセットアップ
Webhookエンドポイントを構成
まず、OmiseダッシュボードでWebhookエンドポイントを構成してください:
- Settings > Webhooksに移動
- Add Webhook Endpointをクリック
- Webhook URLを入力
- 受け取るイベントを選択
- 構成を保存
ローカル開発では、ngrokを使用して公開URLを作成してください。
Webhookイベントを理解
Omiseは次のイベント用のWebhookを送信します:
| イベント | 説明 |
|---|---|
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がWebhookを開発マシンに送信できるようにします。
インストール:
# 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
Webhookテスト用に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はコードを書かずにWebhookをテストするための一時URLを提供します。
ステップ:
- https://webhook.site に移動
- ユニークなURLをコピー (例:
https://webhook.site/abc-123) - このURLをOmiseダッシュボードWebhookに追加
- Omiseでテストイベントをトリガー
- webhook.siteでリアルタイムにリクエストを表示
機能:
- リクエストヘッダー、ボディ、クエリパラメータを表示
- レスポンスステータスとボディを検査
- デバッグ用にレスポンスを編集してカスタマイズ
- リクエストデータをエクスポート
- Webhook URLをチームと共有
Postmanによるテスト
Postman Webhookテスト用にセットアップ
PostmanはテストWebhookリクエストをローカルエンドポイントに送信できます。
Webhookテストコレクションを作成:
[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);
自動Webhookテスト
Jest (JavaScript/Node.js) でのテスト
// webhook.test.js - Webhookハンドラー用の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);
// Webhookが処理されたことを確認
});
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);
// 同じWebhookを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 - Webhookハンドラー用の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):
"""Webhook用の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:
"""Webhookハンドラーをテスト"""
def test_valid_webhook_with_correct_signature(self, client, secret_key):
"""正しいシグネチャで有効なWebhookを受け入れるべき"""
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):
"""べき等的に重複Webhookを処理するべき"""
payload = {
'key': 'charge.complete',
'data': {
'id': 'chrg_test_125',
'amount': 100000,
'currency': 'THB',
'status': 'successful'
}
}
signature = generate_signature(payload, secret_key)
# 同じWebhookを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. Webhookシグネチャを検証
スプーフィングを防ぐためにWebhookシグネチャを常に検証:
<?php
// PHP - Webhookシグネチャ検証
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;
}
// Webhookを処理
$event = json_decode($payload, true);
processWebhook($event);
?>
2. Webhookをべき等に処理
重複配信を安全に処理:
// Go - べき等なWebhook処理
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
}
// Webhookを処理
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秒以内にWebhookに応答:
# Python - 非同期Webhook処理
from flask import Flask, request, jsonify
import threading
import queue
app = Flask(__name__)
webhook_queue = queue.Queue()
def process_webhooks_async():
"""Webhookを処理するためのバックグラウンドワーカー"""
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. Webhookイベントをログ
デバッグ用に包括的なログを実装:
// JavaScript - Webhookログ
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'];
// 受信したWebhookをログ
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' });
}
// Webhookを処理
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. Webhookの状態を監視
Webhook配信と処理を追跡:
# Ruby - Webhookの状態監視
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
トラブルシューティング
一般的な問題
問題: Webhookが受信されていません
考えられる原因:
- OmiseダッシュボードでWebhook URLが構成されていません
- ファイアウォールがリクエストをブロックしています
- ngrokトンネルが実行されていません
- サーバーが正しいポートでリッスンしていません
解決策:
- OmiseダッシュボードでWebhook URLを確認
- サーバーログで受信リクエストを確認
- 最初にwebhook.siteでテスト
- 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はWebhookレスポンスをどのくらい待ちますか?
Omiseはエンドポイントがレスポンスするまで30秒待機します。30秒以内にレスポンスを受け取らない場合、Webhookは失敗としてマークされ、再試行されます。
失敗したWebhookを再試行するにはどうすればよいですか?
OmiseダッシュボードからWebhookを手動で再トリガーできます。設定 > Webhookに移動し、イベントを見つけて、「再送信」をクリックしてください。
Webhookシグネチャを検証する必要がありますか?
はい、偽造を防ぐためにWebhookシグネチャを常に検証してください。Omiseはシークレットキーを使用してHMAC-SHA256ですべてのWebhookに署名します。
テストモードでWebhookをテストできますか?
はい、テストモードは本番環境と同じようにWebhookを送信します。OmiseダッシュボードでテストモードWebhookエンドポイントを構成してください。
関連リソース
- テストカードとデータ - Webhookシナリオ用のテストカード番号
- 失敗のシミュレート - Webhook失敗シナリオのテスト
- Webhookガイド - Webhookのドキュメント
- API認証 - Webhookシグネチャの理解
- エラーコード参照 - Webhookレスポンスのエラーコード
次のステップ
- ローカルWebhookテスト用にngrokをセットアップ
- Webhookハンドラーでシグネチャ検証を実装
- 重複処理を防ぐためにべき等処理を追加
- Webhookハンドラー用の自動テストを記述
- Webhookを非同期で処理してパフォーマンスを改善
- デバッグ用に包括的なログを追加
- Webhookの状態を監視するセットアップを実施
- アプリケーションが処理するすべてのWebhookイベントをテスト
デプロイ準備ができましたか? 本番環境チェックリストを確認してください。
テストデータが必要ですか? テストカードとデータを参照してください。