プラットフォームイベントが発生した際にZenovayからリアルタイム通知を受け取るためのアウトバウンドWebhookを設定する方法と、Zenovayが内部でインバウンド決済Webhookを処理する仕組みについて説明します。
アウトバウンドWebhook
Zenovayは、ユーザーが設定可能なアウトバウンドWebhookをサポートしています。これにより、変更を検出するためにAPIをポーリングする代わりに、お客様のサービスはプラットフォームイベントが発生した瞬間に反応できます。
プランごとの利用可否
API経由でアウトバウンドWebhookを管理するには、個人APIトークンが必要です。APIトークンは有料機能です。Proプラン以上でご利用いただけます(Freeプランではトークンを作成できません)。トークンはSettings → Account → Security & access(app.zenovay.com/settings/account/security)で作成できます。Webhookはトークンが属するチームに限定されます。
Webhookの作成
WebhookはZenovay APIの/v1/cli/mutate/webhooksで管理されます。宛先url(https://である必要があります)と少なくとも1つのイベントタイプを指定してください。POSTリクエストで作成します:
curl -X POST https://api.zenovay.com/v1/cli/mutate/webhooks \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://yoursite.com/zenovay-hook","events":["traffic_spike"]}'
レスポンスにはWebhookのidと署名用のsecretが含まれます。secretは作成時のみ返されますので、すぐに安全な場所に保管してください。紛失した場合はsecretをローテーションする必要があり(下記参照)、元のsecretは復元できません。
Webhookの一覧表示
curl https://api.zenovay.com/v1/cli/mutate/webhooks \
-H "Authorization: Bearer YOUR_TOKEN"
Webhook署名の検証
すべてのWebhook配信は、作成時に取得した署名secretを使用してHMACで署名されます。ペイロードを信頼する前にエンドポイントで署名を検証し、タイミング攻撃に対して脆弱にならないように定数時間比較を使用してください:
import crypto from 'crypto';
function verifyZenovayWebhook(rawBody, signatureHeader, secret) {
// The signature header value is prefixed with the algorithm,
// e.g. "sha256=<hex>". Strip the prefix before comparing.
const received = (signatureHeader || '').replace(/^sha256=/, '');
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// timingSafeEqual throws if the buffers differ in length, so
// length-check first, then compare in constant time.
if (expected.length !== received.length) return false;
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
}
検証時は常に生のリクエストボディ(受信した正確なバイト列)を使用してください。再シリアライズされたバージョンではなく生のボディを使用することで、計算した署名がZenovayが計算した署名と一致するようになります。
署名を運ぶヘッダー名の正確な値は、REST APIリファレンスに記載されています。
Webhookのテスト
エンドポイントが正しく設定されていることを確認するために、合成テスト配信を送信します。レスポンスには配信試行の結果(エンドポイントからのHTTPステータスと所要時間)が含まれます:
curl -X POST https://api.zenovay.com/v1/cli/mutate/webhooks/{id}/test \
-H "Authorization: Bearer YOUR_TOKEN"
署名secretのローテーション
secretが漏洩した場合や定期的にローテーションしたい場合は、ローテーションを行ってください。新しいsecretはレスポンスで1回だけ返されます:
curl -X POST https://api.zenovay.com/v1/cli/mutate/webhooks/{id}/rotate \
-H "Authorization: Bearer YOUR_TOKEN"
ローテーション後は、新しいsecretで検証するようエンドポイントを更新してください。古い署名は直ちに検証されなくなります。
Webhookの取り消し
curl -X DELETE https://api.zenovay.com/v1/cli/mutate/webhooks/{id} \
-H "Authorization: Bearer YOUR_TOKEN"
取り消されたWebhookは直ちに配信を受け取らなくなります。取り消しは元に戻せません。
ベストプラクティス
- 可能な限り早く2xxで応答してください。 まず受領を確認し、その後バックグラウンドジョブでイベントを処理してください。
- 冪等性を保ってください。 ネットワーク再試行により同じイベントが複数回到着する可能性があります — 受信時刻ではなく、イベントのidをキーにハンドラーを設計してください。
- 信頼する前に検証してください。 HMACが検証できないリクエストは拒否し、タイムスタンプが許容範囲から大きく外れているリクエストも拒否してください。
- チーム外の誰かがWebhookの設定やログにアクセスした場合は、secretをローテーションしてください。
インバウンド決済Webhook
Zenovayは、サブスクリプションとインフラを管理するために、外部サービスからのインバウンドWebhookも処理します。これらはZenovayプラットフォーム内で実行され、お客様による設定は不要です。
インバウンドWebhookのソース
| ソース | 目的 |
|---|---|
| Stripe | サブスクリプション管理、決済処理、チェックアウト完了 |
| アップタイム監視 | 外部監視サービスからのヘルスチェックトリガー |
ポーリングの代替手段
アウトバウンドWebhookがユースケースに合わない場合(たとえば、イベントストリームではなく特定時点の集計値が必要な場合など)でも、External APIをポーリングすることでリアルタイム統合を構築できます。
External APIも有料機能です — APIキーが必要で、Freeプランはアクセスできません。以下のエンドポイントは標準的な{ success, data, timestamp }エンベロープを返すため、ペイロードはdataの下にあります。
External APIによるポーリング
const API_KEY = process.env.ZENOVAY_API_KEY;
const WEBSITE_ID = process.env.ZENOVAY_WEBSITE_ID;
async function checkAnalytics() {
const response = await fetch(
`https://api.zenovay.com/api/external/v1/analytics/${WEBSITE_ID}?range=24h`,
{
headers: { 'X-API-Key': API_KEY }
}
);
const { data } = await response.json();
if (data.summary.total_visitors > threshold) {
await sendSlackNotification(data);
}
}
// Poll every 5 minutes
setInterval(checkAnalytics, 5 * 60 * 1000);
rangeパラメータは24h、7d、30d、90d、または1yを受け入れます(デフォルトは7d)。
ライブ訪問者数
APIキーなしで現在の訪問者数を確認できる公開ライブエンドポイントを使用します:
async function getLiveVisitors(trackingCode) {
const response = await fetch(
`https://api.zenovay.com/e/live/${trackingCode}`
);
return await response.json();
}
Slack統合の例
Slackに投稿するスケジュールレポートを構築します:
async function sendDailyReport() {
const response = await fetch(
`https://api.zenovay.com/api/external/v1/analytics/${WEBSITE_ID}?range=24h`,
{
headers: { 'X-API-Key': API_KEY }
}
);
const { data } = await response.json();
const { summary } = data;
await fetch(SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `Daily Analytics Report:\n` +
`Visitors: ${summary.total_visitors}\n` +
`Unique Visitors: ${summary.unique_visitors}\n` +
`Page Views: ${summary.total_page_views}`
})
});
}
CRM統合の例
スケジュールに従ってアナリティクスデータをCRMに同期します:
async function syncToCRM() {
const response = await fetch(
`https://api.zenovay.com/api/external/v1/analytics/${WEBSITE_ID}/visitors`,
{
headers: { 'X-API-Key': API_KEY }
}
);
const { data } = await response.json();
for (const visitor of data.visitors) {
await crm.contacts.update({
country: visitor.country_name,
city: visitor.city,
last_seen: visitor.visited_at
});
}
}
ポーリングのレート制限
ポーリング統合を構築する場合は、External APIのレート制限を尊重してください:
| プラン | リクエスト/分 | 月間制限 |
|---|---|---|
| Free | 10 | 1,000 |
| Pro | 30 | 10,000 |
| Scale | 60 | 100,000 |
| Enterprise | 120 | 1,000,000 |
ポーリングのベストプラクティス
- リクエスト頻度を削減するため、APIレスポンスをローカルにキャッシュする
- 適切なポーリング間隔を使用する(最小5分推奨)
- 制限に達しないよう
X-RateLimit-Remainingヘッダーを監視する - 429レスポンスを受け取った場合は指数バックオフを実装する