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

Webhookのテスト

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

インタラクティブWebhookテスター

このツールを使用して、Webhookイベントを探索し、ペイロード例を表示し、エンドポイントをテストできます:

インタラクティブWebhookテスター

Webhookイベントを探索し、エンドポイントをテスト

Triggered when a charge is completed successfully

POSTcharge.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テスト戦略

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

Webhookテストのセットアップ

Webhookエンドポイントを構成

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

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

ローカル開発では、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を提供します。

ステップ:

  1. https://webhook.site に移動
  2. ユニークなURLをコピー (例: https://webhook.site/abc-123)
  3. このURLをOmiseダッシュボードWebhookに追加
  4. Omiseでテストイベントをトリガー
  5. 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トンネルが実行されていません
  • サーバーが正しいポートでリッスンしていません

解決策:

  1. OmiseダッシュボードでWebhook 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はWebhookレスポンスをどのくらい待ちますか?

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

失敗したWebhookを再試行するにはどうすればよいですか?

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

Webhookシグネチャを検証する必要がありますか?

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

テストモードでWebhookをテストできますか?

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

関連リソース

次のステップ

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

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

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