🟢HubSpot 開発者向け実践教科書 — 2026年版 Developer Edition
Chapter 9  ·  App Architecture & OAuth 2.0

外部連携設計——
App & OAuth

Public App の構造・OAuth 2.0 認証フロー・トークン管理・Scopes 設計・Marketplace 申請まで。複数ポータルに対応した HubSpot App を安全に構築するための設計パターンを体系化する。

OAuth 2.0 Authorization Code
Multi-portal 対応
所要時間:約90分
9-1 Private App vs Public App

用途に応じて認証方式を選択する。

🔑 Private App(アクセストークン)

用途:自社ポータル専用のカスタム開発
認証:Bearer トークン(期限なし)
特徴:シンプル・1ポータルのみ対応
ユースケース:社内ツール・データ同期・自動化スクリプト

🌐 Public App(OAuth 2.0)

用途:複数のお客様ポータルに対応する SaaS 製品
認証:OAuth 2.0(アクセストークン + リフレッシュトークン)
特徴:Marketplace 掲載・マルチテナント対応
ユースケース:ISV 製品・HubSpot 連携 SaaS

判断基準: 自社のHubSpotポータルだけで使うなら Private App。 複数のお客様ポータルで使ってもらう、または Marketplace に掲載したい場合は Public App + OAuth 2.0 を選択します。
9-2 OAuth 2.0 認証フロー

Authorization Code フローでお客様ポータルへのアクセス権を取得する。

  1. 1
    認可リクエスト(ユーザーを HubSpot にリダイレクト) アプリの「Connect with HubSpot」ボタンを押すと、HubSpot の認可画面に遷移。スコープと Client ID を含む URL にリダイレクトする。
  2. 2
    ユーザーが権限を承認 HubSpot の画面で「許可する」をクリック。指定したスコープへのアクセスを承認する。
  3. 3
    認可コードを受け取る(callback URL) HubSpot が指定の redirect_uri にアクセス。URL クエリパラメータに code が含まれる。
  4. 4
    code をトークンに交換 サーバー側で code + Client Secret を使い、アクセストークン + リフレッシュトークンを取得する。
  5. 5
    トークンを安全に保存 ポータル ID をキーにデータベースへ暗号化保存。アクセストークン(有効期間6時間)はリフレッシュトークンで随時更新する。
Node.js / Express — OAuth 2.0 フロー実装
import express from 'express'; import { Client } from '@hubspot/api-client'; const app = express(); const CLIENT_ID = process.env.HUBSPOT_CLIENT_ID; const CLIENT_SECRET = process.env.HUBSPOT_CLIENT_SECRET; const REDIRECT_URI = process.env.HUBSPOT_REDIRECT_URI; const SCOPES = ['crm.objects.contacts.read', 'crm.objects.contacts.write', 'crm.objects.deals.read']; // Step 1: HubSpot の認可ページにリダイレクト app.get('/auth/hubspot', (req, res) => { const authUrl = new URL('https://app.hubspot.com/oauth/authorize'); authUrl.searchParams.set('client_id', CLIENT_ID); authUrl.searchParams.set('redirect_uri', REDIRECT_URI); authUrl.searchParams.set('scope', SCOPES.join(' ')); authUrl.searchParams.set('state', generateCsrfToken(req)); // CSRF 対策 res.redirect(authUrl.toString()); }); // Step 3 & 4: コールバック処理 app.get('/auth/hubspot/callback', async (req, res) => { const { code, state } = req.query; // CSRF 検証 if (!verifyCsrfToken(req, state)) { return res.status(403).send('Invalid state'); } // code → トークン交換 const tokenResponse = await fetch('https://api.hubapi.com/oauth/v1/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: REDIRECT_URI, code, }), }); const tokens = await tokenResponse.json(); // { access_token, refresh_token, expires_in: 21600, hub_id, hub_domain } // Step 5: トークンを DB に保存 await saveTokens(tokens.hub_id, { accessToken: tokens.access_token, refreshToken: tokens.refresh_token, expiresAt: Date.now() + tokens.expires_in * 1000, hubDomain: tokens.hub_domain, }); res.redirect('/dashboard'); });
Node.js — アクセストークンの自動リフレッシュ
async function getValidAccessToken(portalId) { const stored = await db.getTokens(portalId); // 有効期限の5分前に更新 const needsRefresh = stored.expiresAt - Date.now() < 5 * 60 * 1000; if (!needsRefresh) return stored.accessToken; // リフレッシュトークンで新しいアクセストークンを取得 const res = await fetch('https://api.hubapi.com/oauth/v1/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'refresh_token', client_id: CLIENT_ID, client_secret: CLIENT_SECRET, refresh_token: stored.refreshToken, }), }); const newTokens = await res.json(); // 更新されたトークンを DB に保存 await db.updateTokens(portalId, { accessToken: newTokens.access_token, expiresAt: Date.now() + newTokens.expires_in * 1000, }); return newTokens.access_token; } // 使用例:ポータル ID を指定して API クライアントを生成 async function getClientForPortal(portalId) { const accessToken = await getValidAccessToken(portalId); return new Client({ accessToken }); }
9-3 Scopes 設計のベストプラクティス

必要最小限のスコープを要求し、ユーザーの信頼を得る。

スコープ権限用途
crm.objects.contacts.readコンタクト 読み取りコンタクト情報の参照
crm.objects.contacts.writeコンタクト 書き込みコンタクトの作成・更新
crm.objects.deals.read商談 読み取り商談情報の参照
crm.objects.deals.write商談 書き込み商談の作成・更新
crm.schemas.custom.readカスタムオブジェクトスキーマ 読み取りスキーマ構造の確認
contentCMS コンテンツ 読み書きブログ・ページ操作
formsフォーム 読み書きフォーム管理
transactional-emailトランザクションメール送信システムメール
oauthOAuth 基本(必須)すべての OAuth App に必要
最小権限の原則: アプリが必要とする機能に対して最小限のスコープだけを要求してください。 過剰なスコープは Marketplace 審査で指摘され、ユーザーの承認率も下がります。 機能を後から追加する場合は、ユーザーに再認証を求める必要があります。
9-4 Marketplace 申請の準備

HubSpot App Marketplace に掲載するための要件と準備事項を整理する。

📋 Marketplace 申請チェックリスト

技術要件:OAuth 2.0 実装・HTTPS 必須・Webhook 署名検証・適切なエラーハンドリング
セキュリティ:ユーザーデータの最小収集・暗号化保存・GDPR / データ削除対応
UX 要件:インストールフロー(30秒以内)・エラーメッセージの明確さ・アンインストール機能
コンテンツ:アプリの説明・スクリーンショット・デモ動画・サポートドキュメント
テスト:開発者サンドボックスでの動作確認・複数ポータルでのテスト

⚠ アンインストール処理の実装: App がアンインストールされると HubSpot は app.uninstall Webhook を送信します。 このイベントを受け取ったら、そのポータルのトークンと個人データを削除してください。 これは Marketplace 審査の必須要件です。
Node.js — アンインストール Webhook の処理
app.post('/webhook/hubspot', verifySignature, async (req, res) => { res.status(200).json({ received: true }); for (const event of req.body) { if (event.subscriptionType === 'app.uninstall') { const { portalId } = event; // トークンを削除 await db.deleteTokens(portalId); // そのポータルのユーザーデータを削除(GDPR 対応) await db.deletePortalData(portalId); console.log(`Portal ${portalId} uninstalled and data cleaned up`); } } });
9-5 この章のまとめ

✅ Chapter 9 チェックリスト

  • Private App と Public App の使い分けを判断できる
  • OAuth 2.0 Authorization Code フローを5ステップで説明できる
  • CSRF 対策(state パラメータ)を実装できる
  • アクセストークンの自動リフレッシュロジックを実装できる
  • マルチポータル対応のトークン管理設計ができる
  • 最小権限の原則に基づいてスコープを選択できる
  • アンインストール Webhook でデータクリーンアップを実装できる
  • Marketplace 申請の主要要件を理解した
次章(Chapter 10)について:HubSpot MCP Server と AI 開発連携を学びます。MCP Server のセットアップ・Claude Code / Cursor との接続・AI 駆動開発フローを実践レベルで解説します。