HubSpotをただ「使う」のではなく、CMSとCRMをひとつの統合システムとして設計するための技術知識を徹底解説します。トラッキングコードの仕組み・スマートコンテンツ実装・HubSpot API・Webhook設計・外部CMS連携まで、コードレベルで理解できる唯一の章です。
HubSpotがサイト訪問者を「匿名の誰か」から「特定のコンタクト」へと変換する仕組みは、 JavaScriptトラッキングコード + Cookie + フォーム送信の3つの組み合わせです。 この仕組みを完全に理解することが、CMS↔CRM連携設計の出発点です。
CookieがないブラウザでHubSpotトラッキングコードが設置されたページを訪問。この時点ではCRMにデータは登録されない。
フォーム送信データに hubspotutk が自動付与されてHubSpotサーバーへ送信。HubSpotはメールアドレスで既存コンタクトを照合し、新規なら作成・既存なら更新する。
フォーム送信以降、この訪問者がサイトを訪問するたびにCRMの「ページビュー」アクティビティが自動で記録される。コンタクト詳細でリアルタイムにどのページを閲覧しているか確認できる。
コンタクト詳細 → アクティビティ → 「ページビュー」でフィルタリングページビュー・フォーム再送信・メールのリンクのクリック・スコア加算・ワークフロートリガーが連鎖的に機能する。「このコンタクトが今日料金ページを3回見た」等の情報が営業に通知できる。
ワークフロートリガー:「価格ページを閲覧した」→ 担当営業に通知| HubSpot CMS | 外部CMS(WordPress等) | |
|---|---|---|
| トラッキングコードの設置 | ✅ {{ standard_header_includes }} タグで自動挿入。追加作業不要。 |
手動で </body> 直前にJSコードを貼り付けるか、GTMで管理する。 |
| Cookieの管理 | 自動。GDPR設定をONにするとCookie同意バナーも自動表示。 | 別途CookieプラグインとHubSpotのGDPR設定を合わせて管理が必要。 |
| 既知コンタクトの判定 | 自動。{{ request.contact_id }} でHubLから参照可能。 |
不可(サーバーサイドでの判定はAPI経由のみ)。 |
<!-- base_template.html — すべてのページテンプレートが継承するベース --> <html> <head> <!-- ▼ トラッキングコード・CSSを自動挿入(HubSpot管理)--> {{ standard_header_includes }} </head> <body> {% block body %}{% endblock %} <!-- ▼ HubSpotトラッキングコード本体を自動挿入 --> {{ standard_footer_includes }} </body> </html>
// Google Tag Manager に HubSpot トラッキングコードを追加する方法 // タグ:カスタムHTMLタグ / トリガー:全ページ // 設置コード(HubSpot管理画面 → ⚙️設定 → トラッキングとアナリティクス → トラッキングコード からコピー) <script type="text/javascript" id="hs-script-loader" async defer src="//js.hs-scripts.com/YOUR_HUB_ID.js"> </script> // ▼ フォーム送信時に hubspotutk を取得して外部フォームに渡す方法 function getHubspotCookie() { const cookies = document.cookie.split(';'); for (const c of cookies) { const [key, val] = c.trim().split('='); if (key === 'hubspotutk') return val; } return ''; } // → フォームの hidden フィールド hutk にこの値をセットして送信
スマートコンテンツとは、訪問者の属性に応じてページのコンテンツを動的に切り替える機能です。 「まだ購入していない人にはCTAを表示、既存顧客にはサポートリンクを表示」といった パーソナライズが、コードなしで(またはHubLで)実現できます。
{# ライフサイクルステージによってCTAを切り替える実装例 #} {% set lc_stage = contact.lifecyclestage %} {% if lc_stage == "customer" %} <!-- 既存顧客には「サポートページへ」を表示 --> <div class="cta cta--support"> <p>いつもご利用いただきありがとうございます。</p> <a href="/support" class="btn btn--green">サポートページへ</a> </div> {% elif lc_stage == "marketingqualifiedlead" or lc_stage == "salesqualifiedlead" %} <!-- MQL/SQLには「デモを申し込む」を強調表示 --> <div class="cta cta--demo cta--prominent"> <p>製品についてご興味をお持ちの方へ</p> <a href="/demo" class="btn btn--orange">無料デモを申し込む</a> </div> {% elif lc_stage == "lead" %} <!-- Leadには「資料ダウンロード」を表示 --> <div class="cta cta--download"> <a href="/resources/guide" class="btn btn--blue">導入ガイドをダウンロード</a> </div> {% else %} <!-- 未知の訪問者・Subscriberには汎用CTAを表示 --> <div class="cta cta--default"> <a href="/contact" class="btn btn--primary">まずは相談してみる</a> </div> {% endif %}
{# デバイスと国でコンテンツを切り替える実装例 #} {% set is_mobile = request.is_mobile %} {% set country = request.geoip_country_code %} {# モバイル訪問者向け:電話CTAを最優先 #} {% if is_mobile %} <a href="tel:0120-XXX-XXX" class="btn btn--tel"> 📞 今すぐ電話で相談する(無料) </a> {# 日本以外からの訪問者:英語CTAを表示 #} {% elif country != "JP" %} <a href="/en/contact" class="btn btn--en"> Contact us in English </a> {% else %} {# 日本・PCのデフォルト #} <a href="/contact" class="btn btn--default"> お問い合わせ </a> {% endif %}
第4章ではメール内でのパーソナライズトークンを学びましたが、 HubSpot CMS のページテンプレートでも同様の動的コンテンツを実装できます。 ログインしている既知コンタクト専用のパーソナライズされたページが作れます。
{# ======================================================== コンタクトが判明している場合のパーソナライズ実装 request.contact_id が空でなければ既知コンタクト ======================================================== #} {% if request.contact_id %} {# コンタクトのプロパティを取得 #} {% set fname = contact.firstname | default("ゲスト") %} {% set company = contact.company | default("貴社") %} {% set stage = contact.lifecyclestage | default("subscriber") %} {% set score = contact.hubspotscore | default(0) %} <!-- パーソナライズされたヘッダー --> <section class="hero hero--personalized"> <h1>{{ fname }}様、おかえりなさい</h1> <p>{{ company }}向けの最新コンテンツをご用意しました。</p> </section> {# リードスコアに基づくコンテンツ推薦 #} {% if score >= 50 %} <div class="recommendation recommendation--hot"> <p>⚡ {{ fname }}様は現在ホットリードです</p> <a href="/demo-booking">今すぐ専門担当者と話す</a> </div> {% endif %} {% else %} <!-- 未識別の匿名訪問者向けデフォルト表示 --> <section class="hero hero--default"> <h1>業務効率を劇的に改善するCRMツール</h1> <a href="/contact">無料で試してみる</a> </section> {% endif %}
コンタクト情報:contact.firstname / contact.lastname / contact.company / contact.lifecyclestage / contact.hubspotscore / contact.email
リクエスト情報:request.contact_id(既知コンタクトか判定)/ request.is_mobile / request.geoip_country_code / request.path / request.query_dict.xxx(クエリパラメータ取得)
HubSpot CMS 以外で構築したサイト(WordPress・Next.js・Nuxt.js等)にも、 HubSpot フォームを埋め込んでCRMにデータを送ることができます。 埋め込み方法は用途に応じて3パターンあります。
<!-- 1. フォームを表示したい場所にターゲット要素を設置 --> <div id="hubspot-form-container"></div> <!-- 2. HubSpot Forms JS ライブラリを読み込む --> <script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/embed/v2.js"> </script> <!-- 3. フォームを生成 --> <script> hbspt.forms.create({ // ポータルID:HubSpot管理画面のURLから確認(例: app.hubspot.com/contacts/XXXXX) portalId : "YOUR_PORTAL_ID", // フォームID:フォームエディターのURLから確認 formId : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // フォームをレンダリングするDOM要素のセレクタ target : "#hubspot-form-container", // ▼ 主要コールバック onFormReady: function($form) { // フォームがDOMに挿入された後に実行 // hidden フィールドへのUTM値の注入はここで行う const params = new URLSearchParams(window.location.search); ['utm_source','utm_medium','utm_campaign'].forEach(key => { if (params.get(key)) $form.find(`input[name="${key}"]`).val(params.get(key)); }); }, onFormSubmit: function($form) { // 送信ボタンが押された直後(送信完了前)に実行 console.log('フォームが送信されました'); }, onFormSubmitted: function($form, data) { // 送信完了後に実行 → GA4 / Google広告のコンバージョンタグをここで発火 if (typeof gtag !== 'undefined') { gtag('event', 'conversion', { send_to: 'AW-XXXXXXXX/YYYYYYYY' }); } // サンクスページへリダイレクト(フォーム設定のリダイレクトより優先) window.location.href = '/thanks'; } }); </script>
// components/HubSpotForm.tsx import { useEffect, useRef } from 'react'; interface Props { portalId : string; formId : string; onSubmitted?: () => void; } declare global { interface Window { hbspt: any; } } export default function HubSpotForm({ portalId, formId, onSubmitted }: Props) { const ref = useRef<HTMLDivElement>(null); useEffect(() => { // HubSpot Forms JS の動的読み込み const script = document.createElement('script'); script.src = 'https://js.hsforms.net/forms/embed/v2.js'; script.async = true; script.onload = () => { if (window.hbspt && ref.current) { window.hbspt.forms.create({ portalId, formId, target : `#hs-form-${formId}`, onFormSubmitted: () => onSubmitted?.() }); } }; document.head.appendChild(script); // クリーンアップ:コンポーネントアンマウント時にスクリプトを削除 return () => { document.head.removeChild(script); }; }, [portalId, formId]); return <div ref={ref} id={`hs-form-${formId}`} />; } // 使い方 // <HubSpotForm // portalId="12345" // formId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" // onSubmitted={() => router.push('/thanks')} // />
HubSpot API を使うと、外部システムから直接CRMのデータを読み書きできます。 バックエンドでのデータ同期・カスタム連携・CI/CDパイプラインでの自動化に不可欠です。
2022年11月以降、従来のHubSpot APIキー(hapikey)は廃止されました。
現在は「プライベートアプリトークン(Private App Access Token)」を使います。
発行場所:⚙️設定 → インテグレーション → プライベートアプリ → 「プライベートアプリを作成」
スコープ(権限)を必要最小限に設定してから発行し、トークンは環境変数で管理してください。
// hubspot-client.ts // npm install @hubspot/api-client import { Client } from '@hubspot/api-client'; const hubspotClient = new Client({ accessToken: process.env.HUBSPOT_PRIVATE_TOKEN! }); /** * メールアドレスでコンタクトを検索し、存在すれば更新、なければ作成する * (Upsert パターン) */ export async function upsertContact(data: { email : string; firstname?: string; lastname? : string; company? : string; [key: string]: string | undefined; }) { // ① メールアドレスで既存コンタクトを検索 const searchRes = await hubspotClient.crm.contacts.searchApi.doSearch({ filterGroups: [{ filters: [{ propertyName: 'email', operator : 'EQ', value : data.email }] }], properties: ['email', 'firstname', 'lifecyclestage'], limit : 1 }); const properties = Object.fromEntries( Object.entries(data).filter(([_, v]) => v !== undefined) ) as Record<string, string>; if (searchRes.total > 0) { // ② 既存コンタクトがいれば更新(PATCH) const contactId = searchRes.results[0].id; return hubspotClient.crm.contacts.basicApi.update(contactId, { properties }); } else { // ③ 存在しなければ新規作成(POST) return hubspotClient.crm.contacts.basicApi.create({ properties }); } } // 使い方例 await upsertContact({ email : 'yamada@abc-corp.co.jp', firstname: '太郎', lastname : '山田', company : '株式会社ABC', hs_lead_status: 'NEW' // カスタムプロパティも渡せる });
Webhook とは「HubSpot内でイベントが発生した瞬間に、 指定したURLにPOSTリクエストを自動送信する」仕組みです。 ポーリング(定期的なAPI呼び出し)なしでリアルタイム連携が実現できます。
// webhook-server.ts // npm install express crypto import express from 'express'; import crypto from 'crypto'; const app = express(); app.use(express.json()); /** * HubSpot Webhook の署名を検証する(セキュリティ必須) * 設定場所:HubSpot → ⚙️設定 → インテグレーション → プライベートアプリ → Webhook */ function verifyHubSpotSignature( req: express.Request, clientSecret: string ): boolean { const signature = req.headers['x-hubspot-signature-v3'] as string; const timestamp = req.headers['x-hubspot-request-timestamp'] as string; const bodyStr = JSON.stringify(req.body); const sourceStr = `POST${req.protocol}://${req.hostname}${req.path}${bodyStr}${timestamp}`; const expected = crypto .createHmac('sha256', clientSecret) .update(sourceStr) .digest('base64'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // ▼ Webhook 受信エンドポイント app.post('/webhooks/hubspot', (req, res) => { // 1. 署名検証(改ざん防止) if (!verifyHubSpotSignature(req, process.env.HS_CLIENT_SECRET!)) { return res.status(401).send('Invalid signature'); } // 2. ペイロードを処理(配列でイベントが届く) const events: any[] = req.body; for (const event of events) { console.log(`イベント: ${event.subscriptionType}`); switch (event.subscriptionType) { case 'contact.creation': // 新規コンタクト作成 → Slack通知 notifySlack(`新規リード: contactId=${event.objectId}`); break; case 'contact.propertyChange': if (event.propertyName === 'lifecyclestage' && event.propertyValue === 'customer') { // 成約 → 社内DBのステータスを即時更新 updateInternalDB(event.objectId, 'customer'); } break; case 'deal.propertyChange': if (event.propertyName === 'dealstage' && event.propertyValue === 'closedwon') { // 成約 → 請求システムに自動連携 triggerBillingSystem(event.objectId); } break; } } // 3. 必ず 200 を返す(HubSpotが再送しないよう) res.sendStatus(200); });
| 購読できる主要イベント | 説明 |
|---|---|
| contact.creation | 新規コンタクトが作成された時 |
| contact.deletion | コンタクトが削除された時 |
| contact.propertyChange | コンタクトのプロパティが変更された時(どのプロパティかも指定可) |
| deal.creation | 新規取引が作成された時 |
| deal.propertyChange | 取引のプロパティが変更された時(ステージ変更の検知に使う) |
| company.propertyChange | 会社プロパティが変更された時 |
HubSpotを外部サービスと連携する方法は4つのパターンがあります。 開発工数・リアルタイム性・柔軟性のトレードオフを理解して最適な方法を選びます。
GUIでHubSpotと他サービスをつなぐ最もシンプルな方法。6,000以上のアプリと接続できる。
Zapierより高度な条件分岐・ループ・データ変換が可能なノーコード/ローコード連携ツール。
HubSpotが公式に提供している専用の連携機能。設定が最も簡単で安定している。
HubSpot API を直接呼び出すカスタム実装。最も柔軟で、Zapier/Makeでは難しい複雑な処理が可能。
Q1: 接続したいサービスはHubSpotの公式インテグレーションにあるか? YES → ネイティブインテグレーションを使う(最もシンプル・安定) Q2: 開発リソースがなく、できるだけすぐ繋ぎたいか? YES → Zapier または Make(ノーコード) データ変換が不要でシンプル → Zapier 条件分岐・ループが必要 → Make Q3: リアルタイム性(数秒以内の反応)が必要か? YES → Webhook + 自前サーバー(API直接実装) ※ ZapierやMakeは数分〜数十分の遅延が発生することがある Q4: 独自のビジネスロジック・大量データ処理が必要か? YES → API直接実装(@hubspot/api-client 使用推奨) バッチ処理 → 定期実行のCronジョブ イベント駆動 → Webhook受信サーバー Q5: コストを最小限にしたいか? YES → Zapierは有料プランが高い → Make(より安価)またはAPI直接実装
| 連携したいこと | 推奨方法 | 実装難易度 |
|---|---|---|
| HubSpot成約 → Slackに通知 | ネイティブ連携(Slack公式) or Zapier | ⭐(設定5分) |
| Shopify注文 → HubSpot取引作成 | ネイティブ連携(Shopify公式) | ⭐⭐(設定30分) |
| Typeformアンケート → HubSpotプロパティ更新 | Zapier / Make | ⭐⭐(設定1時間) |
| 自社基幹システム ↔ HubSpot 双方向同期 | API直接実装 + Webhook | ⭐⭐⭐⭐(開発2〜5日) |
| Next.js サイトのフォーム → HubSpot CRM | Forms API または Forms JS SDK | ⭐⭐(開発2〜4時間) |
| HubSpot成約 → 請求書自動発行(freee / MF等) | Webhook + API直接実装 | ⭐⭐⭐(開発1〜3日) |
□ HubSpot トラッキングコードがすべてのページに設置されているか
□ フォームの hidden フィールドに hubspotutk が渡されているか(外部CMSの場合)
□ スマートコンテンツでライフサイクルステージ別の表示が設計されているか
□ パーソナライズトークンにすべてデフォルト値が設定されているか
□ API認証は Private App Token で行い、環境変数で管理しているか(APIキーは廃止済み)
□ Webhook受信サーバーで署名検証(x-hubspot-signature-v3)を実装しているか
□ Webhook受信後に必ず 200 を返しているか(返さないとHubSpotが再送し続ける)
□ 外部連携で障害が起きた場合のリトライ・エラーログの仕組みがあるか
トラッキングコードがCookieを発行し、フォーム送信時にメールと紐付く。HubSpot CMSでは自動設置、外部CMSでは手動設置が必要。フォームに hutk を渡す実装を忘れない。
ライフサイクルステージ / リスト / デバイス / 国 / 参照元の5条件でコンテンツを出し分けられる。HubLの {% if contact.lifecyclestage == "customer" %} パターンを使い回す。
hbspt.forms.create() を useEffect でラップした再利用可能なコンポーネントを作成する。onFormSubmitted コールバックで GA4 のコンバージョンタグも同時発火させる。
旧来のAPIキー(hapikey)は2022年廃止。Private App Tokenをスコープ最小限で発行し、環境変数(.env)で管理する。コードに直書きは絶対NG。
x-hubspot-signature-v3 ヘッダーをHMAC-SHA256で検証しないと、第三者からの偽リクエストを処理してしまう。受信後は必ず 200 を返す(返さないと無限再送が発生する)。
公式連携あり→ネイティブ。開発リソースなし→Zapier/Make。リアルタイム必須→Webhook+API直接。独自ロジック→API直接実装。無闇にZapierを使わず、まず公式連携を確認する。