HubSpotカスタムコードアクション活用法|Node.js/Pythonで実装する高度なワークフロー自動化

この記事の結論

カスタムコードアクションの利用要件と基本的な設定手順。Node.jsでの実装パターン(外部API呼び出し、データ変換、CRM操作)。

ブログ目次

記事の内容を、そのまま実務に落とし込みたい方向け

HubSpot導入、AI活用、CRM整備、業務効率化までをまとめて支援しています。記事で気になったテーマを、そのまま相談ベースで整理できます。


カスタムコードアクションの利用要件と基本的な設定手順。Node.jsでの実装パターン(外部API呼び出し、データ変換、CRM操作)。

「HubSpotの標準ワークフローアクションでは、やりたいことが実現できない」「外部APIとの連携を自動化したいが、Zapierを挟むと遅延やコストが気になる」——HubSpotのワークフローを本格的に活用すると、標準機能だけでは対応しきれないケースに必ず遭遇します。

HubSpotのカスタムコードアクションは、ワークフロー内でNode.jsまたはPythonのコードを直接実行できる機能です。Data Hub Professional以上(旧Data Hub Professional以上)のプランで利用可能で、外部APIの呼び出し、複雑なデータ変換、条件分岐ロジックなど、標準アクションでは不可能な処理をワークフローに組み込めます。

本記事では、カスタムコードアクションの基本設定から、Node.js・Pythonでの具体的な実装パターン、エラーハンドリング、実務での活用ユースケースまでを体系的に解説します。


この記事でわかること

HubSpotのワークフローで標準アクションでは実現できない処理を自動化したい開発者・CRM管理者に向けた記事です。

  • カスタムコードアクションの利用要件と基本的な設定手順 — ワークフローエディタで「アクションを追加」→「カスタムコード」を選択します。
  • Node.jsでの実装パターン(外部API呼び出し、データ変換、CRM操作) — 外部のデータエンリッチメントAPIを呼び出し、コンタクト情報を自動補完する例です。
  • Pythonでの実装パターン(計算ロジック、通知送信、データ処理)
  • エラーハンドリングとデバッグのベストプラクティス — エラーが発生した場合でも、callback(Node.js)またはreturn(Python)で必ず出力を返すように実装してください。
  • 実務で使える5つのユースケースと設計の考え方 — フォーム送信時に、メールアドレスから企業情報を自動取得してCRMに補完します。

カスタムコードアクションの概要

利用要件

HubSpotワークフロー(カスタムコードアクション)

カスタムコードアクションを利用するには、以下の条件を満たす必要があります。

項目 要件
プラン Data Hub Professional以上(旧Data Hub Professional以上)
対応言語 Node.js 20(JavaScript)、Python 3.9
実行時間制限 最大20秒
メモリ制限 128MB
シークレット 最大5個(APIキー等の安全な保管)
利用可能なnpmパッケージ axios, @hubspot/api-client, lodash 等
出力データ 最大5つの出力変数(後続アクションで使用可能)

標準アクションとの使い分け

カスタムコードアクションは強力な機能ですが、標準アクションで実現できる処理には使う必要はありません。以下の表で使い分けを判断してください。

処理内容 標準アクション カスタムコード
プロパティ値の更新 推奨 不要
メール送信 推奨 不要
単純な条件分岐 推奨 不要
外部API呼び出し 不可 必須
複雑なデータ変換・計算 不可 必須
複数レコードの一括処理 不可 必須
カスタムバリデーション 不可 必須
動的な条件分岐ロジック 限定的 推奨

カスタムコードアクションの設定手順

ステップ1:ワークフローにアクションを追加

ワークフローエディタで「アクションを追加」→「カスタムコード」を選択します。言語(Node.jsまたはPython)を選び、コードエディタが表示されます。

ステップ2:入力プロパティの定義

コード内で使用するCRMプロパティを「プロパティを含める」セクションで定義します。たとえば、コンタクトのメールアドレスや会社名をコード内で参照する場合、ここで対象プロパティを選択します。

ステップ3:シークレットの設定

外部APIのAPIキーやトークンは、「シークレット」として安全に保管します。シークレットはコード内で環境変数として参照でき、ワークフローの設定画面にプレーンテキストで表示されることはありません。

ステップ4:コードの記述とテスト

コードエディタにロジックを記述し、「テスト」ボタンで動作を確認します。テスト実行時には実際のCRMレコードを選択して、リアルなデータで検証できます。

ステップ5:出力変数の定義

後続のワークフローアクションで使用するデータを「出力」として定義します。外部APIから取得したスコアやステータスを出力変数に格納し、次の条件分岐で使用する連携が可能です。


Node.jsでの実装パターン

パターン1:外部APIを呼び出してデータエンリッチメント

外部のデータエンリッチメントAPIを呼び出し、コンタクト情報を自動補完する例です。

const axios = require('axios');

exports.main = async (event, callback) => {
  const email = event.inputFields['email'];
  const apiKey = process.env.ENRICHMENT_API_KEY;

  try {
    const response = await axios.get(
      `https://api.clearbit.com/v2/people/find`,
      {
        params: { email: email },
        headers: { Authorization: `Bearer ${apiKey}` }
      }
    );

    const { company, title } = response.data;

    callback({
      outputFields: {
        enriched_company: company?.name || '',
        enriched_title: title || '',
        enrichment_status: 'success'
      }
    });
  } catch (error) {
    callback({
      outputFields: {
        enriched_company: '',
        enriched_title: '',
        enrichment_status: 'failed'
      }
    });
  }
};

パターン2:取引データに基づくカスタムスコアリング

取引金額、ステージ、経過日数を組み合わせた独自のスコアリングロジックです。

exports.main = async (event, callback) => {
  const dealAmount = parseFloat(event.inputFields['amount']) || 0;
  const dealStage = event.inputFields['dealstage'];
  const createDate = new Date(event.inputFields['createdate']);
  const now = new Date();
  const daysSinceCreation = Math.floor(
    (now - createDate) / (1000 * 60 * 60 * 24)
  );

  let score = 0;

  // 金額ベースのスコア
  if (dealAmount >= 10000000) score += 40;
  else if (dealAmount >= 5000000) score += 30;
  else if (dealAmount >= 1000000) score += 20;
  else score += 10;

  // ステージベースのスコア
  const stageScores = {
    'appointmentscheduled': 10,
    'qualifiedtobuy': 20,
    'presentationscheduled': 30,
    'decisionmakerboughtin': 40,
    'contractsent': 50
  };
  score += stageScores[dealStage] || 0;

  // 経過日数によるペナルティ
  if (daysSinceCreation > 90) score -= 10;
  if (daysSinceCreation > 180) score -= 20;

  const priority = score >= 60 ? 'high'
    : score >= 30 ? 'medium' : 'low';

  callback({
    outputFields: {
      deal_score: score.toString(),
      deal_priority: priority
    }
  });
};

パターン3:HubSpot APIで関連レコードを取得

コンタクトに関連する会社情報をAPIで取得し、プロパティを更新する例です。

const hubspot = require('@hubspot/api-client');

exports.main = async (event, callback) => {
  const hubspotClient = new hubspot.Client({
    accessToken: process.env.HUBSPOT_TOKEN
  });

  const contactId = event.object.objectId;

  try {
    const associations = await hubspotClient.crm.contacts
      .associationsApi.getAll(contactId, 'companies');

    if (associations.results.length > 0) {
      const companyId = associations.results[0].id;
      const company = await hubspotClient.crm.companies
        .basicApi.getById(companyId, [
          'industry', 'numberofemployees', 'annualrevenue'
        ]);

      callback({
        outputFields: {
          company_industry: company.properties.industry || '',
          company_size: company.properties.numberofemployees || '',
          company_revenue: company.properties.annualrevenue || ''
        }
      });
    } else {
      callback({
        outputFields: {
          company_industry: '',
          company_size: '',
          company_revenue: ''
        }
      });
    }
  } catch (error) {
    callback({
      outputFields: {
        company_industry: 'error',
        company_size: '',
        company_revenue: ''
      }
    });
  }
};

Pythonでの実装パターン

パターン1:LTV(顧客生涯価値)の自動計算

def main(event):
    monthly_revenue = float(
        event.get('inputFields', {}).get('monthly_revenue', 0)
    )
    churn_rate = float(
        event.get('inputFields', {}).get('churn_rate', 5)
    ) / 100
    gross_margin = float(
        event.get('inputFields', {}).get('gross_margin', 70)
    ) / 100

    if churn_rate > 0:
        avg_lifespan_months = 1 / churn_rate
        ltv = monthly_revenue * gross_margin * avg_lifespan_months
    else:
        ltv = monthly_revenue * gross_margin * 120

    if ltv >= 10000000:
        segment = 'enterprise'
    elif ltv >= 3000000:
        segment = 'mid-market'
    elif ltv >= 500000:
        segment = 'smb'
    else:
        segment = 'starter'

    return {
        "outputFields": {
            "calculated_ltv": str(int(ltv)),
            "customer_segment": segment,
            "avg_lifespan_months": str(int(avg_lifespan_months))
        }
    }

パターン2:取引ステージ変更時のSlack通知

import os
import json
from urllib.request import Request, urlopen
from urllib.error import URLError

def main(event):
    deal_name = event.get('inputFields', {}).get('dealname', '不明')
    deal_amount = event.get('inputFields', {}).get('amount', '0')
    deal_stage = event.get('inputFields', {}).get('dealstage', '不明')

    slack_webhook = os.environ.get('SLACK_WEBHOOK_URL')

    message = {
        "text": "取引ステージ変更通知",
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                        f"*取引名:* {deal_name}\n"
                        f"*金額:* {deal_amount}\n"
                        f"*新ステージ:* {deal_stage}"
                    )
                }
            }
        ]
    }

    try:
        req = Request(
            slack_webhook,
            data=json.dumps(message).encode('utf-8'),
            headers={'Content-Type': 'application/json'}
        )
        urlopen(req)
        status = 'sent'
    except URLError as e:
        status = f'failed: {str(e)}'

    return {
        "outputFields": {
            "notification_status": status
        }
    }

エラーハンドリングのベストプラクティス

必ず対策すべきエラーパターン

エラーパターン 原因 対処法
タイムアウト(20秒超過) 外部APIの応答遅延 タイムアウト設定、リトライ上限
API認証エラー(401/403) トークン期限切れ、権限不足 シークレットの定期更新、エラー通知
レート制限(429) API呼び出し頻度超過 リトライ待機、バッチ処理
データ型エラー null値、予期しない型 デフォルト値設定、型チェック
メモリ超過 大量データの処理 データの分割処理、不要変数の解放

エラーが発生した場合でも、callback(Node.js)またはreturn(Python)で必ず出力を返すように実装してください。エラー時の出力にステータス情報を含めることで、後続のワークフローアクションで条件分岐による制御が可能になります。


実務で使える5つのユースケース

ユースケース1:リードエンリッチメント

フォーム送信時に、メールアドレスから企業情報を自動取得してCRMに補完します。ClearbitやApolloなどのエンリッチメントAPIと連携し、会社名、従業員数、業種、売上規模などを自動入力します。

ユースケース2:カスタムリードスコアリング

HubSpotの標準スコアリングでは対応できない、独自の重み付けロジックを実装します。行動データ(ページ閲覧、メール開封)とデモグラフィックデータ(企業規模、業種)を組み合わせた多変量スコアリングが可能です。

ユースケース3:外部システムへのリアルタイムデータ同期

取引のステージ変更や新規コンタクト作成をトリガーに、基幹システムやERPにリアルタイムでデータを同期します。REST API呼び出しで、HubSpotを起点とした双方向のデータ連携を実現します。

ユースケース4:動的な通知・アラート制御

取引金額、顧客セグメント、チームの状況に応じて、通知の内容や送信先を動的に切り替えます。標準の通知アクションでは固定的な通知しかできませんが、カスタムコードなら条件に応じた柔軟な制御が可能です。

ユースケース5:データ品質の自動チェック

CRMに入力されたデータの品質を自動検証し、不正データを検出・修正します。電話番号のフォーマット統一、住所の正規化、重複検出アラートなどに活用できます。


制約事項と注意点

セキュリティの考慮

APIキーやトークンは必ず「シークレット」機能を使って保管してください。コード内にハードコードすると、ワークフローの編集権限を持つすべてのユーザーに露出します。また、外部APIへの通信はHTTPS(TLS 1.2以上)を使用することが必須です。

パフォーマンスの最適化

20秒の実行時間制限があるため、大量のデータ処理や複数の外部API呼び出しを1回のアクションで行うことは推奨されません。処理が重い場合は、複数のカスタムコードアクションに分割するか、非同期処理の設計を検討してください。


あわせて読みたい


まとめ

カスタムコードアクションは、Node.jsまたはPythonを使ってHubSpotワークフロー内に独自ロジックを組み込める機能で、外部API連携や複雑な条件分岐、データ変換など、標準ワークフローでは実現できない処理を安全にCRMへ組み込めます。

安定運用の鍵になるのは、エラーハンドリングと20秒のタイムアウト制限を踏まえた実装設計です。外部API呼び出しの失敗時の挙動や、処理時間を短縮する工夫を最初から組み込んでおく必要があります。まずは住所の正規化のような簡単なデータ変換処理を題材にカスタムコードアクションを実装し、動作とエラー時の挙動を一通り検証してみてください。


よくある質問(FAQ)

Q1. カスタムコードアクションで使用できるライブラリに制限はありますか?

Node.jsの場合、axios、@hubspot/api-client、lodashなど一部のnpmパッケージが利用可能です。任意のパッケージをインストールすることはできません。Pythonの場合も、標準ライブラリに加えて一部の外部ライブラリが利用可能です。最新の対応パッケージリストはHubSpotの公式ドキュメントで確認してください。

Q2. デバッグはどのように行いますか?

ワークフローエディタの「テスト」機能で、実際のCRMレコードを使ったテスト実行が可能です。console.log(Node.js)やprint(Python)の出力はテスト結果画面に表示されるため、変数の値やAPIレスポンスの確認に活用できます。ワークフローの実行履歴からも各アクションの入出力値を確認できます。

Q3. カスタムコードが失敗した場合、ワークフローは停止しますか?

デフォルトでは、カスタムコードアクションがエラーで失敗すると、それ以降のワークフローステップは実行されません。エラーハンドリングを適切に実装し、エラー時にもcallback/returnで出力を返すようにすれば、後続アクションを条件分岐で制御できます。

Q4. Node.jsとPython、どちらを選ぶべきですか?

外部API連携やHubSpot APIの操作にはNode.jsが適しています。@hubspot/api-clientパッケージが利用できるため、HubSpot APIとの連携がスムーズです。一方、数値計算やデータ分析のロジックにはPythonが適しています。チームの技術スタックに合わせて選択してください。


HubSpotのカスタムコードアクションは、標準ワークフローの制約を超えた高度な自動化を実現する強力な機能です。外部API連携、複雑なデータ処理、カスタムロジックの実装により、HubSpotを企業の業務自動化基盤として最大限に活用できます。まずは小さなユースケースから始めて、段階的に活用範囲を広げていくことを推奨します。

カテゴリ: HubSpotエンタープライズ | HubSpot


株式会社StartLinkは、事業推進に関わる「販売促進」「DXによる業務効率化(ERP/CRM/SFA/MAの導入)」などのご相談を受け付けております。 サービスのプランについてのご相談/お見積もり依頼や、ノウハウのお問い合わせについては、無料のお問い合わせページより、お気軽にご連絡くださいませ。

関連キーワード:

サービス資料を無料DL

著者情報

7-1

今枝 拓海 / Takumi Imaeda

株式会社StartLink 代表取締役。累計150社以上のHubSpotプロジェクト支援実績を持ち、Claude CodeやHubSpotを軸にしたAI活用支援・経営基盤AXのコンサルティング事業を展開。
HubSpotのトップパートナー企業や大手人材グループにて、エンタープライズCRM戦略策定・AI戦略ディレクションを経験した後、StartLinkを創業。現在はCRM×AIエージェントによる経営管理支援を専門とする。