🟢HubSpot 開発者向け実践教科書 — 2026年版 Developer Edition
Chapter 8  ·  Webhooks & Event-Driven Design

Webhook &
イベント駆動設計

HubSpot Webhook の設定・署名検証による本物確認・べき等処理・外部システム連携パターン。リアルタイムで CRM イベントに反応するシステムを安全に構築する。

Webhook API v3
署名検証必須
所要時間:約75分
8-1 HubSpot Webhook の仕組み

HubSpot でイベントが発生すると、登録した URL に POST リクエストが送信される。

📡 Webhook の動作フロー

① HubSpot でイベント発生(コンタクト作成・プロパティ変更・商談ステージ移動など)
② HubSpot が登録した Webhook URL に POST リクエストを送信
③ 受信サーバーが 200 OK を返す(5秒以内)
④ 200 以外の場合は最大3回リトライ(指数バックオフ)
⑤ リトライ後も失敗すると HubSpot は送信を諦める(イベントが失われる可能性あり

購読できるイベント説明
contact.creationコンタクト新規作成
contact.deletionコンタクト削除
contact.propertyChangeコンタクトのプロパティ変更
company.creation会社新規作成
deal.creation商談新規作成
deal.propertyChange商談のプロパティ変更(ステージ変更など)
ticket.creationチケット新規作成
contact.mergeコンタクトのマージ
8-2 署名検証(必須)

Webhook リクエストが本物の HubSpot から来たものかを検証する。

🔐 署名検証の仕組み

HubSpot は Webhook 送信時、リクエストヘッダーに X-HubSpot-Signature-v3 を付与します。 これはリクエスト情報(HTTP メソッド・URL・タイムスタンプ・ボディ)と アプリのクライアントシークレットを使った HMAC-SHA256 署名です。 受信側でこの署名を再計算して一致することを確認することで、 なりすまし・中間者攻撃を防ぎます。

Node.js / Express — 署名検証ミドルウェア
import crypto from 'crypto'; import express from 'express'; const app = express(); // raw body を保持する(署名検証に使うため JSON パース前が必要) app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } })); function verifyHubSpotSignature(req, res, next) { const signature = req.headers['x-hubspot-signature-v3']; const timestamp = req.headers['x-hubspot-request-timestamp']; if (!signature || !timestamp) { return res.status(401).json({ error: 'Missing signature' }); } // タイムスタンプが5分以上古い場合は拒否(リプレイ攻撃防止) const MAX_AGE_MS = 5 * 60 * 1000; if (Math.abs(Date.now() - parseInt(timestamp)) > MAX_AGE_MS) { return res.status(401).json({ error: 'Request too old' }); } // 署名を再計算 const method = req.method.toUpperCase(); const url = `https://${req.hostname}${req.originalUrl}`; const body = req.rawBody?.toString('utf8') ?? ''; const source = method + url + body + timestamp; const expected = crypto .createHmac('sha256', process.env.HUBSPOT_CLIENT_SECRET) .update(source) .digest('base64'); // タイミング攻撃を防ぐための定数時間比較 const isValid = crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); if (!isValid) { return res.status(401).json({ error: 'Invalid signature' }); } next(); } // Webhook エンドポイントに署名検証ミドルウェアを適用 app.post('/webhook/hubspot', verifyHubSpotSignature, handleWebhook);
8-3 べき等処理パターン

同じ Webhook が複数回届いても安全に処理できるべき等(Idempotent)な設計をする。

🔄 なぜべき等処理が必要か

HubSpot は Webhook 送信に At-Least-Once(少なくとも1回)の保証を提供します。 ネットワーク障害などで200 OK が届かなかった場合、同じイベントが再送されます。 べき等処理(同じ操作を何度実行しても結果が変わらない設計)がないと、 データの重複作成・二重課金・重複メール送信などの問題が発生します。

Node.js — べき等キーによる重複排除
import { createClient } from 'redis'; const redis = createClient({ url: process.env.REDIS_URL }); await redis.connect(); async function handleWebhook(req, res) { // HubSpot の各イベントは一意の eventId を持つ const events = req.body; // Webhook は配列で届く // 即座に 200 OK を返す(5秒タイムアウト対策) res.status(200).json({ received: true }); // 処理は非同期で行う for (const event of events) { const idempotencyKey = `webhook:${event.eventId}`; // Redis で処理済みかチェック(TTL: 24時間) const alreadyProcessed = await redis.set( idempotencyKey, '1', { NX: true, EX: 86400 } // NX: 存在しない場合のみセット ); if (!alreadyProcessed) { console.log(`Duplicate event skipped: ${event.eventId}`); continue; } // 処理本体 await processEvent(event); } } async function processEvent(event) { const { subscriptionType, objectId, propertyName, propertyValue } = event; switch (subscriptionType) { case 'contact.creation': await onContactCreated(objectId); break; case 'deal.propertyChange': if (propertyName === 'dealstage' && propertyValue === 'closedwon') { await onDealClosed(objectId); } break; } }
8-4 外部システム連携パターン

HubSpot Webhook を使った実践的な外部連携パターンを学ぶ。

📊 SFA → HubSpot 同期

外部 SFA(Salesforce など)でコンタクトが更新されたとき、Webhook 経由で HubSpot の該当コンタクトを自動更新。双方向同期の場合は更新元を識別し、無限ループを防ぐ必要がある。

💳 決済サービス連携

Stripe の Webhook(支払い成功・解約)を受信し、HubSpot のカスタムオブジェクト(subscriptions)を更新。ライフサイクルステージも自動変更する。

Node.js — 成約イベントで外部システムを更新
async function onDealClosed(dealId) { const client = new HubspotClient({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN }); // 商談の詳細と関連コンタクトを取得 const [deal, contacts] = await Promise.all([ client.crm.deals.basicApi.getById(dealId, ['dealname', 'amount', 'hubspot_owner_id']), client.crm.associations.v4.basicApi.getPage('deals', dealId, 'contacts'), ]); const contactIds = contacts.results.map(r => r.toObjectId); // コンタクトのライフサイクルを "customer" に更新 await client.crm.contacts.batchApi.update({ inputs: contactIds.map(id => ({ id, properties: { lifecyclestage: 'customer' } })), }); // 社内 Slack に成約通知 await notifySlack({ channel: '#sales-wins', text: `🎉 成約: ${deal.properties.dealname} — ¥${Number(deal.properties.amount).toLocaleString()}`, }); // ERP システムに受注データを連携 await createErpOrder({ dealId, amount: deal.properties.amount, contactIds, }); }
⚠ 即時 200 レスポンス + 非同期処理: Webhook ハンドラは 5秒以内に 200 を返す必要があります。 重い処理(DB 操作・外部 API 連携)は非同期キュー(BullMQ・SQS など)に投げて、 レスポンスとは分離してください。
8-5 この章のまとめ

✅ Chapter 8 チェックリスト

  • HubSpot Webhook のイベント種別と At-Least-Once 保証を理解した
  • X-HubSpot-Signature-v3 による署名検証を Node.js で実装できる
  • タイムスタンプ検証(5分以内)でリプレイ攻撃を防げる
  • Redis + NX フラグによるべき等キーで重複処理を排除できる
  • 即時 200 レスポンス + 非同期処理の設計パターンを理解した
  • 成約・決済イベントを起点にした外部システム連携を設計できる
次章(Chapter 9)について:外部連携設計——App と OAuth を学びます。Public App の構造・OAuth 2.0 フロー・Marketplace 申請・Scopes 設計を実践的に解説します。