n8nとkintone・Salesforce・HubSpot連携:問い合わせを顧客管理に入れる

公開 書いた人 大林 諒

「問い合わせ受付」ワークフローは、Webの問い合わせをGoogleスプレッドシート「問い合わせ一覧」に残してSlackの #問い合わせ に知らせるところまでできています。この記事では、その問い合わせを社内の顧客管理(kintone・Salesforce・HubSpot)にも入れます。同じ人から2回目の問い合わせが来ても顧客が2人に増えないよう、既にいれば更新し、いなければ登録する形で作ります。逆向きの、顧客管理で何か起きたらn8nを動かす方法も3つそれぞれで書きます。前提はn8n 2.44.7(2026年10月時点の安定版)がn8n.example.co.jpのような独自ドメインとHTTPSで動いていることです。HTTP Requestノードの基本は「n8nで外部サービスとつなぐ:Webhook・HTTP Request・API」に書いています。

この記事の内容
  1. 先に決めること
  2. 3つの顧客管理の比べ方
  3. ワークフローの形
  4. kintoneに入れる
  5. Salesforceに入れる
  6. HubSpotに入れる
  7. 顧客管理の側から動かす
  8. 応用
  9. できないことと、その代わり
  10. よく詰まるところ
  11. 次にやること

先に決めること

どこを正にするか

顧客の情報は顧客管理を正にし、スプレッドシート「問い合わせ一覧」は受けた記録に役割を下げます。担当者や対応状況を両方で直す運用は、数週間で食い違います。

同じ人をどう見分けるか

新規か更新かを決める鍵はメールアドレスにします。会社名は「株式会社サンプル」と「(株)サンプル」のように揺れ、氏名には同姓同名がいます。照合の前に、前後の空白を落として小文字にそろえます。

上書きする項目と、しない項目

項目新規のとき既存のとき
会社名・氏名入れる入れる(新しい情報とみなす)
最新の問い合わせ・最終問い合わせ日入れる上書きする
担当者・対応状況・商談の段階初期値に任せる触らない

担当者や商談の段階を連携で上書きすると、営業が手で直した内容が問い合わせのたびに消えます。顧客管理には連携専用のユーザーかAPIの鍵を用意し、個人のアカウントでつながないようにします。担当者が辞めてアカウントを止めた日に連携も止まるからです。

3つの顧客管理の比べ方

専用ノードの有無

サービスn8nのノード顧客管理側から動かすトリガーノードで足りないとき
kintone無い無いHTTP Requestで kintone REST API を呼ぶ
Salesforce「Salesforce」(Lead・Contact・Task・Search など12種)「Salesforce Trigger」(一定間隔で取りに行く)HTTP Request の「Predefined Credential Type」で同じCredentialを使う
HubSpot「HubSpot」(Contact・Company・Deal・Engagement など)「HubSpot Trigger」(開発者用アプリが要る)同上

kintoneにはコミュニティ製のノードもありますが、公式ではなく、n8nの版が上がったときに動き続ける保証がありません。HTTP RequestならAPIの仕様どおりに書けます。

認証の方式

サービス方式n8nのCredential鍵の範囲
kintoneAPIトークン「Header Auth」(Name に X-Cybozu-API-Token)アプリごと。閲覧・追加・編集などを選ぶ
SalesforceOAuth2(またはOAuth2 JWT)「Salesforce OAuth2 API」ログインしたユーザーの権限
HubSpotサービスキー(またはOAuth2)「HubSpot Service Key」鍵を作るときに選んだスコープ

HubSpotはUIで作る非公開アプリ(private app)を従来型の扱いに移し、サービスキーを勧めています。サービスキーは2026年10月時点で公開ベータです。HubSpotノードの「Authentication」に残っている「API Key」は、HubSpot側で廃止済みなので使いません。Credentialの共有と権限は「n8nの認証情報(Credential):OAuth2・APIキー・共有と権限」に書いています。

つなぐ手間と上限

kintoneSalesforceHubSpot
準備APIトークンを生成し、アプリを更新External Client App を作り、OAuthでつなぐサービスキーを作り、スコープを選ぶ
新規か更新か重複禁止のフィールドを鍵にUPSERTで1回先に検索(SOQL)してIfで分ける「Create or Update」がメールで判定
顧客管理側から動かすアプリのWebhookSalesforce Trigger、またはフローからHTTPで呼ぶワークフローの「Send a webhook」
APIの上限1アプリ1日10,000件(スタンダードコース)、同時100まで(1ドメイン)24時間あたり100,000件+ライセンス数に応じた分(Enterprise など)10秒に100〜190件(1アプリ)、1日250,000件から(アカウント全体)

問い合わせが1日20件ほどなら、どれも上限は問題になりません。当たるのは一定間隔で取りに行く作りのときで、1分ごとなら取りに行くだけで1日1,440回になります。

完成した「問い合わせ受付」ワークフローの全体(「整形」から顧客管理へ入れる枝が分かれている状態)
完成した「問い合わせ受付」ワークフローの全体(「整形」から顧客管理へ入れる枝が分かれている状態)
完成した「問い合わせ受付」ワークフローの全体(「整形」から顧客管理へ入れる枝が分かれている状態)

ワークフローの形

受けたデータを整える「Edit Fields (Set)」の名前を「整形」に変え、ここから顧客管理へ入れる枝を分けます。スプレッドシートとSlackの間に直列で入れないのは、顧客管理が落ちていても記録と通知を止めないためです。後ろのノードからは {{ $('整形').item.json['メール'] }} で受けた値を取れます。この書き方は「n8nの式(Expression)とデータの扱い:$json・ノードの参照・日付・JSON」に書いています。「整形」には照合用のメールを足します。

項目式
メール{{ $json.body.email.trim().toLowerCase() }}

kintoneに入れる

例は「顧客管理」アプリ(アプリID 12)に、会社名・氏名・メール・最新の問い合わせ・最終問い合わせ日・登録元・対応状況のフィールドがある形で、フィールドコードはフィールド名と同じ日本語にしてあります。

メールを重複禁止にし、APIトークンを作る

UPSERTモード(あれば更新、無ければ登録)の鍵にできるのは、「値の重複を禁止する」を設定した「文字列(1行)」か「数値」のフィールドだけです。

  1. アプリの設定の「フォーム」タブで、メールのフィールドの「値の重複を禁止する」にチェックを入れる。既に重複があると設定できないので先に片付ける。
  2. 「設定」タブの「カスタマイズ/サービス連携」から「APIトークン」を開き、「生成する」を押す。
  3. 「アクセス権」で「レコード閲覧」「レコード追加」「レコード編集」にチェックを入れる。UPSERTは追加と編集の両方が要る。
  4. 「保存」を押し、「アプリを更新」まで押す。押し忘れるとトークンが効かない。
  5. n8nで「Header Auth」のCredentialを作り、Nameに X-Cybozu-API-Token、Valueにトークンを入れる。

APIトークンで入れたレコードは、作成者が「Administrator」になります。「登録元」に Web問い合わせ と入れておくと、後で数えられます。cybozu.comでIPアドレスの制限をかけている会社は、n8nのサーバーのIPを許可するか、Basic認証のヘッダーを足します。

UPSERTで登録か更新を1回でやる

1件用のAPIではなく、複数のレコードを更新するAPI(records.json)に "upsert": true を付けます。

  1. 「整形」の後ろにHTTP Requestを足し、名前を「kintoneに登録」にする。
  2. 「Method」を PUT、「URL」を https://example.cybozu.com/k/v1/records.json にする。
  3. 「Authentication」を「Generic Credential Type」、「Generic Auth Type」を「Header Auth」にし、上のCredentialを選ぶ。
  4. 「Send Body」をオンにし、「Body Content Type」を「JSON」、「Specify Body」を「Using JSON」にして下を入れる。
{
  "app": 12,
  "upsert": true,
  "records": [
    {
      "updateKey": { "field": "メール", "value": "{{ $json['メール'] }}" },
      "record": {
        "会社名": { "value": {{ JSON.stringify($json['会社名']) }} },
        "氏名": { "value": {{ JSON.stringify($json['氏名']) }} },
        "最新の問い合わせ": { "value": {{ JSON.stringify($json['本文']) }} },
        "最終問い合わせ日": { "value": "{{ $now.setZone('Asia/Tokyo').format('yyyy-LL-dd') }}" },
        "登録元": { "value": "Web問い合わせ" }
      }
    }
  ]
}
kintoneに登録するHTTP Requestの設定(Method: PUT、Bodyに上のJSON)
kintoneに登録するHTTP Requestの設定(Method: PUT、Bodyに上のJSON)

メールがあればそのレコードが更新され、無ければ新しく作られます。record に書かなかったフィールドは、更新なら元の値のまま、新規ならアプリの初期値になります。対応状況を書かないのはこのためです。返事の records[0].operation が INSERT(新規)か UPDATE(更新)かで結果が分かります。1件用のAPI(record.json)にはupsertの指定がありません。

新規か既存かで通知を変える

  1. 「kintoneに登録」の後ろにIfを足す。
  2. 条件を {{ $json.records[0].operation }} が INSERT と等しい(String の「is equal to」)にする。
  3. trueのSlackには「新しいお客様」、falseには「既存のお客様から再度」と書き分ける。
  4. 文面にレコードのURL https://example.cybozu.com/k/12/show#record={{ $json.records[0].id }} を入れる。
Ifでoperationを見て2つの出口に分けた状態
Ifでoperationを見て2つの出口に分けた状態

既存のデータに重複が残っていてメールを重複禁止にできないときは、先に探します。HTTP Requestを GET で records.json に向け、「Send Query Parameters」で app に 12、query に メール = "{{ $json['メール'] }}" limit 2 を入れます。0件なら追加、1件なら records[0].$id.value で更新、2件ならどちらを直すか機械では決められないのでSlackで人に確かめます。値に " や \ が入るときは \" のようにエスケープします。

Salesforceに入れる

Salesforceは、まだ商談になっていない人をリード(Lead)、取引のある会社の人を取引先責任者(Contact)で持つのが標準です。取引先責任者かリードにいればToDoを足し、どちらにもいなければリードを作ります。

つなぐ

Credentialは「Salesforce OAuth2 API」、Salesforce側は「アプリケーションマネージャー」の「New External Client App」で作ります。手順は「n8nで外部サービスとつなぐ:Webhook・HTTP Request・API」と同じで、コールバックURLはセルフホストなら https://n8n.example.co.jp/rest/oauth2-credential/callback、n8n Cloudなら https://oauth.n8n.cloud/oauth2/callback です。つなぐときにログインしたユーザーの権限がそのままn8nの権限になるので、リード・取引先責任者・ToDoの作成と参照だけを許可した連携専用のユーザーでつなぎます。Professional EditionはAPIを有効にした契約でないとつながらないので、先に確かめます。

取引先責任者とリードを探す

  1. 「整形」の後ろにSalesforceノードを足し、名前を「取引先責任者を探す」にする。
  2. 「Resource」を「Search」、「Operation」を「Query」にし、「Query」に下の1つ目を入れる。
  3. ノードの「Settings」で「Always Output Data」(結果が無くても空のアイテムを出す)をオンにする。
  4. 後ろにIfを足し、{{ $json.Id }} が空でない(String の「is not empty」)を条件にする。
  5. falseの出口に、2つ目のSOQLで同じ形の「リードを探す」とIfをもう1組置く。
SELECT Id, Name, AccountId FROM Contact WHERE Email = '{{ $('整形').item.json['メール'] }}' LIMIT 1
SELECT Id, Name FROM Lead WHERE Email = '{{ $('整形').item.json['メール'] }}' AND IsConverted = false LIMIT 1
SalesforceノードでResource: Search、QueryにSOQLを入れた状態
SalesforceノードでResource: Search、QueryにSOQLを入れた状態

3を忘れると、見つからなかったときにSearchが何も出さず、そこでワークフローが終わります。新しいお客様ほど登録されない、という気づきにくい壊れ方です。IsConverted = false は取引先責任者に変換済みのリードを拾わないためです。

Lead にも「Create or Update」(upsert)があり、「Match Against」で照合する項目を選べます。選べるのは外部IDかIDルックアップの性質を持つ項目だけで、同じ値が2件以上あるとSalesforceはエラーを返します。リードと取引先責任者の両方を見るなら、探してから分けるほうが確実です。

いなければリード、いればToDo

  1. 「リードを探す」の後ろのIfのfalseにSalesforceノードを足し、「Resource」を「Lead」、「Operation」を「Create」にする。
  2. 「Company」に会社名、「Last Name」に氏名を入れる。
  3. 「Additional Fields」から「Email」「Description」「Lead Source Name or ID」を足し、メール・本文と、一覧から「Web」を選ぶ。
  4. 2つのIfのtrueを、Salesforceノード「ToDoを足す」にまとめてつなぐ。「Resource」は「Task」、「Operation」は「Create」。
  5. 「Status Name or ID」を一覧から選び、「Additional Fields」で「Who ID」に {{ $json.Id }}、「Subject Name or ID」に Web問い合わせ、「Description」に本文を入れる。
Lead の Create で Company・Last Name と Additional Fields を入れた状態
Lead の Create で Company・Last Name と Additional Fields を入れた状態

リードの必須は「Last Name」(姓)と「Company」です。フォームが姓と名を分けていないなら、氏名をまるごと「Last Name」に入れます。空白で割って姓と名にする式は、「山田太郎」のように空白の無い入力で崩れます。「Who ID」はToDoを付ける人(リードか取引先責任者)のIDです。

HubSpotに入れる

HubSpotはメールアドレスでコンタクトを見分けるので、「Create or Update」が新規と更新を1回でやります。

  1. HubSpotで「Development」の「Keys」から「Service keys」を開き、「Create service key」を押す(スーパー管理者か開発者ツールの権限が要る)。
  2. 「Add new scope」で crm.objects.contacts.read と crm.objects.contacts.write を選び、「Update」、「Create」と押す。
  3. キーを「Show」で表示してコピーし、n8nの「HubSpot Service Key」のCredentialに貼る。
  4. 「整形」の後ろにHubSpotノードを足し、「Credential」で上のCredentialを選び、「Resource」を「Contact」、「Operation」を「Create or Update」にする。
  5. 「Email」にメール、「Contact Properties」から「Company Name」「Last Name」「Message」を足して会社名・氏名・本文を入れる。
HubSpotノードでContact の Create or Update を設定した状態
HubSpotノードでContact の Create or Update を設定した状態

返事にはコンタクトのID vid と、新しく作ったかどうかの isNew が入り、Ifで通知を分けられます。対応のタスクは、後ろのHubSpotノードで「Resource」を「Engagement」、「Type」を「Task」にし、「Associations」の「Contact IDs」に {{ $json.vid }} を入れて作ります。Engagementで作れるのは通話・メール・会議・タスクで、メモ(ノート)はありません。

「Lifecycle Stage Name or ID」(ライフサイクルステージ)は入れません。APIからは前の段階に進めることしかできず、戻すには先に値を空にする必要があるので、商談中のお客様の段階を連携が動かす事故になります。サービスキーは半年ごとの入れ替えが勧められていて、「Rotate and expire later」なら古いキーは7日後に切れます。その間にCredentialを差し替えます。

顧客管理の側から動かす

kintoneで対応状況が「完了」になったらSlackに知らせる、Salesforceでリードができたらシートに足す、といった逆向きの連携です。

kintoneのWebhook

  1. n8nでWebhookを置き、「HTTP Method」を POST、「Path」を推測されにくい長い文字列にする。
  2. kintoneのアプリの設定の「設定」タブ、「カスタマイズ/サービス連携」から「Webhook」を開いて追加する。
  3. URLの欄にn8nのProduction URLを貼り、先頭の https:// を消す。
  4. 通知を送信する条件で「レコードの編集」などを選び、有効にして保存し、「アプリを更新」を押す。

届く内容は body.type(ADD_RECORD・UPDATE_RECORD・UPDATE_STATUS など)、body.app.id、body.record で、値は {{ $json.body.record['対応状況'].value }} のように取ります。kintoneのWebhookにはヘッダーで鍵を付ける設定が無いので、Pathを長くし、最初のIfで body.app.id が 12 かを確かめます。1アプリに10個まで、通知はドメイン全体で1分に60回までで、CSVの読み込み、一括削除、複数のレコードを扱うREST APIでの操作では送られません。n8nが同じアプリを1件用のAPIで更新すると、その通知がまた届いて繰り返し動くことがあるので、n8nからの更新は上の records.json の形で書きます。裏返すと、n8nが records.json で入れた登録や更新ではkintoneのWebhookは動きません。新しいお客様の通知は、kintoneのWebhookで拾わずに「問い合わせ受付」の中で出します。

Salesforce

方法速さ向いている場面
n8nの「Salesforce Trigger」取りに行く間隔しだい数分の遅れが許される通知・転記
フローの「HTTP Callout」でn8nのWebhookを呼ぶフローが動いたとき条件をSalesforce側で絞りたい

「Salesforce Trigger」はn8nが一定間隔で取りに行くノードで、「Trigger On」で「Lead Created」などを選び、「Poll Times」で間隔を決めます。取りに行くたびにAPIを使うので、毎分にせず5〜10分にします。間隔を待たずに動かしたいときは、フローのHTTP Calloutでn8nのProduction URLを呼びます。認証は名前付き認証情報(と外部認証情報)で行い、フローの側ではリクエストヘッダーを足せないので、n8nの「Header Auth」で確かめる鍵は認証情報の側に持たせます。300以上のステータスが返るとエラーになり、フローの障害パスに流れます。フローの作り方は姉妹サイトのSalesforce自作ガイドに書いています。アウトバウンドメッセージもn8nのWebhookで受けられます。SOAPで送られ、受け取れなければ最大24時間、間隔を空けて送り直され、過ぎると捨てられます。受け取ったことを返す応答が要り、n8n側でSOAPのXMLを読み解く手間もかかるので、新しく作るならHTTP Calloutのほうが組みやすいです。n8nがSalesforceを更新すると「Lead Updated」がその更新も拾うので、最終更新者が連携専用のユーザーなら止めるIfを入れます。

HubSpotのワークフロー

HubSpotのワークフローの「Send a webhook」アクションで呼びます。使えるのはData HubのProfessionalかEnterpriseです(2026年10月時点)。

  1. n8nでWebhookを置き、「Authentication」を「Header Auth」にして、Nameに X-Hubspot-Key、Valueに長いランダムな文字列を入れたCredentialを選ぶ。
  2. HubSpotの「Automation」の「Workflows」でワークフローを開き、「+」から「Data ops」の「Send a webhook」を選ぶ。
  3. メソッドを POST、URLにn8nのProduction URL(HTTPSのみ)を入れる。
  4. 認証でAPIキーを選び、名前に X-Hubspot-Key、値に1と同じ文字列を入れて、ヘッダーで送る設定にする。
  5. テストを送り、n8nのExecutionsに届いたのを確かめてから保存する。

失敗した送信は最長3日まで送り直されますが、4XXの返事は送り直されません(429だけは送り直す)。n8nが落ちていても3日以内に戻れば取りこぼさない一方、鍵の設定違いで401を返していた間の分は戻りません。n8nの「HubSpot Trigger」は、HubSpotの開発者アカウントでアプリを作って「HubSpot Developer API」のCredentialを用意する必要があり、2つ目のトリガーを有効にすると前のトリガーが止まります。ワークフローの契約が無いなら、Schedule TriggerとHubSpotノードの「Get Recently Created/Updated」で取りに行きます。

応用

「n8nでAIエージェントを作る:ChatGPT・Claude・Geminiをつなぐ」で判定した問い合わせの種類を、顧客管理の選択肢の項目にも入れられます。AIの答えは選択肢と1文字でも違うと入らないので、間にSwitchを置いて決まった選択肢に寄せ、当たらないものは「その他」にします。顧客管理へ入れる部分はサブワークフローに切り出しておくと、顧客管理を替えても「問い合わせ受付」本体は直さずに済みます(「n8nの定期実行とサブワークフロー:Schedule Triggerと部品化」)。スプレッドシート「問い合わせ一覧」には全件を残し、顧客管理のノードが失敗したらエラーの出口からSlackに知らせます。流し直すときの元の記録になります。シート側の重複は「n8nとGoogleスプレッドシート連携:読み書き・更新・重複を防ぐ」、エラー通知は「n8nを止めずに運用する:アップデート・バックアップ・エラー通知・脆弱性」に書いています。

できないことと、その代わり

できないこと代わりの手
kintoneを専用ノードで扱うHTTP Requestで kintone REST API を呼ぶ
kintoneのWebhookにヘッダーの鍵を付けるPathを推測されにくくし、body.app.id を確かめる
CSV読み込みや一括操作をkintoneのWebhookで拾うSchedule Triggerで 更新日時 > "2026-10-26T00:00:00+09:00" のクエリで取りに行く
Salesforce Triggerで間隔を待たずに動かすフローのHTTP Calloutでn8nのWebhookを呼ぶ
HubSpot Triggerを複数のワークフローで使うワークフローの「Send a webhook」か、Scheduleで取りに行く
HubSpotノードでメモ(ノート)を作るHTTP RequestのPredefined Credential TypeでAPIを呼ぶ
表記の揺れた会社名を機械で同じ会社とみなすメールで照合し、会社のまとめは人が画面でやる

よく詰まるところ

症状原因直し方
kintoneで一部の項目だけ空フィールド名で書いた。存在しないフィールドコードは無視される設定画面でフィールドコードを確かめる。フィールド名を変えてもコードは変わらない
kintoneのドロップダウンでエラー選択肢と全角・半角や空白が違う選択肢の文字をコピーして使う
kintoneで権限エラートークンのアクセス権不足、または「アプリを更新」を押していない追加と編集にチェックを入れ、アプリを更新する
ルックアップのあるアプリでエラー参照先のアプリのトークンが無い参照先のトークンも X-Cybozu-API-Token にカンマ区切りで並べる(9個まで)
kintoneのWebhookが届かないアプリを更新していない、Test URLのままアプリを更新し、Production URLに替える
Salesforceで新しいお客様だけ登録されないSearchが0件で何も出さず止まった「Always Output Data」をオンにする
Salesforceで選択リストのエラー実際に出会った例では、画面の表示ラベルを送っていた一覧から選ぶか、選択リスト値のAPI参照名を送る
Salesforceでリードの作成が重複のエラーで止まる実際に出会った例では、組織の重複ルールが同じメールのリードを止めていた探してから分ける作りにする。ルールは外さない
n8n CloudからSalesforceにつなげない連携ユーザーにアプリを承認する権限が無いプロファイルに「Approve Connected Apps for Non-Admins」を付ける
HubSpotのカスタムプロパティに入らない表示名を書いたプロパティの設定で内部名と選択肢の内部の値を確かめる
HubSpotが429を返す10秒あたりの上限。検索は1秒5回までHTTP RequestのBatchingで間隔を空け、Retry On Failを足す
作った直後のHubSpotのコンタクトが検索に出ない検索に出るまで少し時間がかかる検索せず「Create or Update」の返事の vid を使う

詰まったら、Executionsで失敗した実行を開き、顧客管理のノードの出力にあるエラーの本文を読みます。kintoneなら code と message に原因が書かれています。SalesforceとHubSpotも、エラーの本文に相手のサービスが返した理由が出ます。

次にやること

  • n8nで外部サービスとつなぐ:Webhook・HTTP Request・API
  • n8nとGoogleスプレッドシート連携:読み書き・更新・重複を防ぐ
  • n8nの認証情報(Credential):OAuth2・APIキー・共有と権限
  • n8nの式(Expression)とデータの扱い:$json・ノードの参照・日付・JSON

あわせて読む