Skip to content
Kintone と HubSpot の並行運用 — 増分同期で破綻させない設計

Kintone と HubSpot の並行運用 — 増分同期で破綻させない設計

最終更新日: / 公開日:

最終更新日: / 公開日:

「Kintone から HubSpot へ完全移行」という選択肢は、営業現場の業務フローを止めるリスクが大きく、失敗事例も多くあります。私たち humbulls はある業務用美容機器メーカーの実装で、Kintone と HubSpot を 並行運用 する設計を採用しました。Kintone の更新を Apps Script で日次に増分同期し、HubSpot に流し込む。これで「移行ゼロリスク + 段階移行可」の状態を作りました。Pillar 記事「HubSpot Starter で Professional 級ダッシュボード」シリーズ #6 として、Kintone を含む既存 CRM との並行運用設計を公開します。

1. なぜ移行ではなく並行運用なのか — リスクを 1/10 に抑える

SaaS 完全移行は、業務フローを止めるリスクと営業現場の抵抗が同時に発生します。並行運用なら両方を回避できます。

CRM 移行プロジェクトの失敗パターンは、業界誌や Reforge 等の SaaS 移行記事でも繰り返し報告されています。多くが「移行スケジュールが押す → 営業現場が旧システムに留まる → 新システムが空回り」というパターンに陥ります。※ 本記事の円換算はすべて 1 ドル = 150 円 (2026 年 5 月時点) を基準に計算しています。

実務での選択肢は 3 つです。Kintone を停止して HubSpot に完全移行する (= 移行リスク 大)、Kintone を残したまま HubSpot は試験運用に留める (= HubSpot のメリットを享受できない)、または Kintone と HubSpot を 並行運用 し、データを Apps Script で自動同期する (= 移行リスクほぼゼロ)。私たち humbulls は 3 つ目を選びました。

並行運用にすると以下のメリットが得られます:

観点 完全移行 並行運用
移行リスク 大 (= 業務停止リスク) ほぼゼロ
営業現場の操作変更 必須 (= 抵抗大) 不要 (= Kintone のまま)
HubSpot のメリット享受 完全 即時 (マーケ / 分析側から)
ロールバック 困難 いつでも可
段階移行 不可 (一発勝負)

「並行運用は二重管理で運用負荷が増える」と感じるかもしれませんが、Apps Script で自動同期すれば実質的な手動運用は発生しません。営業はこれまで通り Kintone を使い、マーケと経営は HubSpot のダッシュボードを見る、という役割分担が成立します。

注意点がひとつ。並行運用は「いつ完全移行するか」を明確にしないと、永遠に並行運用のまま放置されるリスクがあります。私たちは「6 ヶ月後にレビュー、12 ヶ月後に完全移行判断」というスケジュールを最初に切ることで、運用が漂流するのを防いでいます。

🤖 AI で実行する — 使ったプロンプト / ワークフロー

目的: 完全移行 / 並行運用 / 試験運用の意思決定マトリクスを生成 所要時間: 25 分 ツール: Claude (Opus 4.7)

プロンプト:

あなたは BtoB SaaS の CRM 移行コンサルタントです。既存 CRM (Kintone / Salesforce / Notion) から HubSpot への移行方針を「完全移行 / 並行運用 / 試験運用」で比較してください。

【判断材料】
- 既存 CRM の利用年数 (短期: 1-3 年 / 中期: 3-5 年 / 長期: 5 年以上)
- 営業組織の規模 (5-10 名 / 11-30 名 / 30 名以上)
- 業務フローの安定度 (定型化済 / カスタマイズ多 / 流動的)
- 移行予算 (= プロジェクト人月)

【出力】 推奨方針 + 判断根拠 3 行 + 失敗リスクの定量化 (= 業務停止日数の見込み)

運用 Tips: - 既存 CRM 利用 5 年以上 + 営業 11 名以上なら並行運用が安全 - 完全移行を選ぶ場合は、移行期間中の「予備の Kintone ライセンス」を 3 ヶ月分契約しておくと事故時に戻せる

📚 参考文献


2. 増分同期の設計 — KINTONE_LAST_SYNC + 失敗時 retry

全件同期は重く、エラー復旧が困難です。「前回の同期時刻からの差分だけ取る」増分同期が、運用負荷を 1/100 に圧縮します。

増分同期 (= 前回の同期時刻以降に更新されたレコードだけ取得する方式) は、CRM 連携の運用負荷を劇的に下げます。Kintone REST API は「更新日時 > 前回同期時刻」のクエリ条件で増分取得が可能です。これを使えば、1.3 万件のフルデータがあっても日次の差分は数十件程度なので、API 呼び出しコストが 1/100 になります。

従来の実装は「毎朝 全件取得して HubSpot に upsert」という設計が多用されていました。動くには動きますが、Kintone 側の API 呼び出し制限 (= 100 req/分) に引っかかりやすく、HubSpot 側の rate limit (= 100 req/10 秒) も同時に圧迫します。万一どこかでエラーが出ると、どこまで同期できたかわからなくなり、リカバリーに半日かかることが普通です。

私たち humbulls の運用では、KINTONE_LAST_SYNC という変数を PropertiesService に保存し、毎朝この時刻以降の差分だけを取得する設計にしています:

function syncKintoneToHubSpot() {
  const props = PropertiesService.getScriptProperties();
  const lastSync = props.getProperty('KINTONE_LAST_SYNC') || '2020-01-01T00:00:00+0900';
  const now = Utilities.formatDate(new Date(), 'JST', "yyyy-MM-dd'T'HH:mm:ssXXX");

  try {
    const records = fetchKintoneIncremental(lastSync);
    Logger.log(`Fetched ${records.length} records since ${lastSync}`);

    const successes = upsertContactsToHubSpot(records);
    // 全件成功時のみ LAST_SYNC を更新 = 失敗時は次回再試行
    if (successes === records.length) {
      props.setProperty('KINTONE_LAST_SYNC', now);
    } else {
      Logger.log(`Partial success: ${successes}/${records.length}. Skipping LAST_SYNC update.`);
    }
  } catch (e) {
    Logger.log(`Sync failed: ${e.message}. Skipping LAST_SYNC update.`);
    throw e;
  }
}

設計のポイントは 2 つあります。LAST_SYNC の更新は 全件成功時のみ 行うこと。失敗時に更新してしまうと、次回の差分取得から失敗分が漏れます。もうひとつは初回起動時のフォールバック ('2020-01-01T00:00:00+0900') を入れておくこと。初回だけは全件取得になるので、これを過去履歴一括 import と組み合わせれば過不足なく回ります (= 過去履歴 import は章 4 で解説)。

Kintone ↔ HubSpot 並行運用フロー

注意点がひとつ。Apps Script の PropertiesService はスクリプト単位で値を保持するので、別スクリプトから参照できません。複数のスクリプトで LAST_SYNC を共有したい場合は、共通の Sheets セルに書き込む設計に切り替えてください。

🤖 AI で実行する — 使ったプロンプト / ワークフロー

目的: 増分同期の冪等性を保証する設計テンプレートを生成 所要時間: 20 分 ツール: Claude Code

プロンプト:

Apps Script で Kintone から HubSpot へ増分同期するコードを書いてください。

【要件】
- 前回同期時刻を PropertiesService に KINTONE_LAST_SYNC として保存
- 「更新日時 > LAST_SYNC」の Kintone レコードのみ取得
- HubSpot Contacts に email キーで upsert (= 存在すれば patch、無ければ post)
- 全件成功時のみ LAST_SYNC を更新 = 失敗時は次回再試行
- 失敗時のログ (Logger.log) を sync_log シートにも書き込み

【出力】 syncKintoneToHubSpot() + fetchKintoneIncremental() + upsertContactsToHubSpot()

運用 Tips: - LAST_SYNC のタイムゾーンを必ず明示 (+0900 を含む ISO 8601 文字列) - 失敗時の通知は Slack Webhook へ流すと気付きやすい (= 翌日まで気付かないと差分が膨らむ)

📚 参考文献


3. ハマりポイント 3 つ — ISO 8601 / email 重複 / Private App scopes

動いたつもりが 403 で止まる、データが入らない、重複が発生する。実装後に半日溶ける典型的な罠を、最初から潰しておきましょう。

Kintone × HubSpot 連携を Apps Script で組むと、公式ドキュメントには明記されていない実装上のハマりが必ず 3 つ出てきます。これを事前に潰しておかないと、運用開始後に「なぜか同期が止まる」「なぜか重複が発生する」という症状で半日溶けます。

従来このハマりは、エンジニアが手探りで原因を特定し、ログを仕込んで再現させて、修正版を再デプロイする、という流れで対応されてきました。1 つあたり半日、3 つで 1.5 日溶ける計算です。

私たち humbulls の実装では、以下 3 つを最初からコードに組み込んでいます:

ハマり 症状 対策
Kintone は ISO 8601 datetime 必須 2026-05-01 00:00:00 で送ると 400 2026-05-01T00:00:00+0900 形式に整形 (T 区切り + タイムゾーン明示)
HubSpot Contacts は email ユニーク 同じ email を post すると 409 Conflict email で Search → 存在すれば patch、無ければ post に切替
HubSpot Private App scopes 不足 post / patch で 403 Forbidden Private App 設定で crm.objects.contacts.write を追加

コードで対応すると、それぞれ以下のような形になります:

// ハマり 1: ISO 8601 整形
function toIso8601(dateStr) {
  return Utilities.formatDate(new Date(dateStr), 'JST', "yyyy-MM-dd'T'HH:mm:ssXXX");
}

// ハマり 2: email upsert (存在すれば patch、無ければ post)
function upsertContactByEmail(record) {
  const searchRes = UrlFetchApp.fetch(`${HUBSPOT_BASE}/crm/v3/objects/contacts/search`, {
    method: 'post',
    headers: { Authorization: `Bearer ${HUBSPOT_TOKEN}` },
    contentType: 'application/json',
    payload: JSON.stringify({
      filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: record.email }] }]
    }),
    muteHttpExceptions: true
  });
  const existing = JSON.parse(searchRes.getContentText()).results[0];

  if (existing) {
    return patchContact(existing.id, record);
  } else {
    return createContact(record);
  }
}

// ハマり 3: Private App scopes
// HubSpot UI → Settings > Integrations > Private Apps > Edit > Scopes
// 必須スコープ: crm.objects.contacts.read / crm.objects.contacts.write

ハマりポイント 3 つの修正コード

crm.objects.contacts.write 等のスコープは Private App 作成時にチェックを入れ忘れがちなので、Token 発行時に必ず確認してください。スコープ不足は実行時の 403 でしか気付けないので、運用に乗せる前にテスト同期で必ず検証します。

注意点がひとつ。muteHttpExceptions: true を必ず付けてください。これを忘れると 4xx エラー時に Apps Script が例外を投げてスクリプトが止まり、後段の upsert が動かなくなります。

🤖 AI で実行する — 使ったプロンプト / ワークフロー

目的: ハマりポイント 3 つを最初から潰したコードを生成 所要時間: 30 分 ツール: Claude Code

プロンプト:

Apps Script で Kintone → HubSpot Contacts upsert を書いてください。以下 3 つのハマりに対応してください。

【ハマり 1】 Kintone は ISO 8601 datetime 必須 → "yyyy-MM-dd'T'HH:mm:ssXXX" 形式に整形
【ハマり 2】 HubSpot Contacts は email ユニーク → Search → patch / post 切替
【ハマり 3】 HubSpot Private App scopes 不足 → 必須スコープを README に列挙

【入力】
- Kintone レコード: kintone_id, name, email, phone, updated_at
- HubSpot プロパティ: kintone_id (Custom), firstname, lastname, email, phone

【出力】 upsertContactByEmail() + toIso8601() + 必須スコープリスト

運用 Tips: - email が空のレコードは upsert スキップ (= HubSpot 側で識別キーに使えない) - 同期失敗時の通知に対象 kintone_id を含めると、再同期がしやすい

📚 参考文献


4. 過去履歴一括 import の別パス — Python で offset 10,000 制限を突破

増分同期だけだと過去データが入りません。初回だけは Python で一括 import するハイブリッド構成が、最短ルートです。

増分同期を組んだ瞬間から「未来のデータ」は自動で HubSpot に流れますが、過去データは別途 import する必要があります。Apps Script の 6 分制限内で 1.3 万件を一気に処理するのは不可能なので、過去履歴 import は Apps Script ではなく Python で動かすのが現実的です。

従来は Apps Script で「1 日 100 件ずつ処理 → Trigger で 130 日かけて移行」というやり方も取られてきました。動くには動きますが、運用開始まで半年近く待つことになります。間に Kintone のスキーマ変更が入ると整合性も崩れます。

私たち humbulls は Python で一括 import + 翌日から Apps Script で増分同期 というハイブリッド構成を採用しました。Python なら 6 分制限がなく、Kintone と HubSpot の両方の API 制限を考慮しながらバッチで投入できます。1.3 万件で約 1 時間で完了します:

# fetch_kintone_full.py — Kintone 全件取得
import requests

def fetch_all_kintone_records(app_id, query=""):
    records = []
    offset = 0
    while True:
        # Kintone offset は 10,000 上限 → $id 範囲で分割が必要
        url = f"https://{SUBDOMAIN}.cybozu.com/k/v1/records.json"
        params = {
            'app': app_id,
            'query': f'{query} order by $id asc limit 500 offset {offset}',
        }
        r = requests.get(url, headers={'X-Cybozu-API-Token': KINTONE_TOKEN}, params=params)
        batch = r.json()['records']
        if not batch:
            break
        records.extend(batch)
        if len(batch) < 500:
            break
        offset += 500
        if offset >= 9500:  # offset 上限間際で $id 分割に切替
            last_id = batch[-1]['$id']['value']
            return records + fetch_all_kintone_records(app_id, f'$id > {last_id}')
    return records

Kintone の offset 制限 (= 10,000) を超える場合は、$id の範囲で分割クエリを発行する形に切り替えます。これで件数上限なく全件取得できます。

取得したデータを HubSpot に投入する import_full_history.py も並行して走らせ、Kintone のフェッチと HubSpot 投入を非同期で進めます。1.3 万件で約 1 時間、Python ローカル実行で完了します。

注意点がひとつ。一括 import の前に 必ず HubSpot 側でバックアップ を取ってください。HubSpot は API 経由の一括削除も可能なので、トラブル時はバックアップから復元することになります。Companies / Contacts / Deals の CSV エクスポートで十分です。

🤖 AI で実行する — 使ったプロンプト / ワークフロー

目的: Kintone offset 10,000 制限を $id 範囲分割で突破する Python スクリプトを生成 所要時間: 40 分 ツール: Claude Code

プロンプト:

Python で Kintone REST API から全レコードを取得するスクリプトを書いてください。

【要件】
- Kintone offset 上限 10,000 を $id 範囲分割で突破
- 1 回の取得は 500 件 (offset = 500 ずつ)
- 取得結果を CSV に出力
- requests + tqdm で進捗バー表示
- 取得後、HubSpot Contacts に upsert する別スクリプトも生成

【出力】
- fetch_kintone_full.py (Kintone 全件 fetch)
- import_full_history.py (HubSpot 投入)
- requirements.txt

運用 Tips: - Python は Apps Script より高速で 6 分制限もないが、初回 import の 1 度きりに留める - 過去履歴 import 後の運用は Apps Script の増分同期に任せる (= Python は import 後に削除して OK)

📚 参考文献


5. 段階移行のロードマップ — Kintone → HubSpot のシフト計画

並行運用は「永遠に並行運用」になりがちです。最初から 12 ヶ月のシフト計画を切ると、漂流を防げます。

並行運用を始めた瞬間は、両 CRM のメリットを取れる蜜月状態に見えます。ただ放置すると「マーケは HubSpot、営業は Kintone」のまま 2 年経過し、データが二重管理のまま固定化される、というアンチパターンに陥りやすくなります。

従来の移行プロジェクトでは「いつ完全移行するか」が空白のまま走り始め、結局完全移行されないまま運用が固定化する事例が多々あります。「並行運用が便利だから残す」が「並行運用しか選べない状態」になり、後から負債化します。

私たち humbulls は 3 段階 / 12 ヶ月のロードマップ を最初から切る運用にしています:

フェーズ 期間 内容
Phase 1: 並行運用立ち上げ 0-3 ヶ月 Kintone → HubSpot 増分同期確立 / Looker Studio ダッシュボード稼働
Phase 2: 部分移行 3-6 ヶ月 マーケ業務を HubSpot に移行 (MA / フォーム / メール配信)、営業は Kintone 継続
Phase 3: 完全移行判断 6-12 ヶ月 営業現場のフィードバック収集、HubSpot UI 操作トレーニング、Kintone 撤退 or 並行運用継続を判断

段階移行ロードマップ

Phase 1 で「データの統合」を完了し、Phase 2 で「マーケ業務の主軸を HubSpot に」、Phase 3 で「営業現場の判断」を行うという順序です。営業現場が一番抵抗が大きいので、最後に持ってきます。

12 ヶ月後の判断は 「Kintone を完全撤退するか / 並行運用を継続するか」 の 2 択です。私たちの実装事例では、営業現場の習慣を尊重して「並行運用継続」を選ぶ組織が多いです。HubSpot のマーケ・分析機能は享受しつつ、営業の日常は Kintone のまま、というハイブリッドが現実解になります。

注意点がひとつ。完全撤退を選ぶ場合は、Kintone のライセンス解約タイミングと HubSpot の年間契約タイミングを揃えてください。Kintone は月額課金ですが、HubSpot は年間前払いの場合が多く、解約のタイミングを間違えると 1 年分の HubSpot 費用が無駄になります。

🤖 AI で実行する — 使ったプロンプト / ワークフロー

目的: 自社の 12 ヶ月段階移行ロードマップを生成 所要時間: 40 分 ツール: Claude (Opus 4.7)

プロンプト:

あなたは BtoB CRM 移行のプロジェクトマネージャーです。Kintone から HubSpot への段階移行を、自社の以下の前提で 12 ヶ月ロードマップに落とし込んでください。

【自社情報】
- 現在の CRM 利用年数 (Kintone 何年使ったか)
- 営業組織の規模 (人数)
- マーケ / 営業 / 分析 の業務量比率
- 完全移行の意向 (高 / 中 / 低)

【出力】
- Phase 1 / 2 / 3 のタスク一覧 (各フェーズ 5-7 タスク)
- 各タスクの担当 (マーケ / 営業 / 情シス / 経営)
- 各タスクの所要工数 (人日)
- リスクと回避策

運用 Tips: - Phase 2 の「マーケ業務移行」中に営業現場へのトレーニングを並行で始めると、Phase 3 がスムーズ - 12 ヶ月後に「並行運用継続」を選んだ場合は、再度 12 ヶ月ロードマップを引き直す (= 漂流防止)

📚 参考文献


6. Salesforce や他 CRM への横展開 — 同じパターンが流用できる

「差分取得 → upsert」のパターンは Kintone 限定ではありません。Salesforce / Notion / AirTable でも、ほぼ同じ設計で並行運用が組めます。

Kintone と HubSpot の並行運用設計は、「既存 CRM の更新日時クエリで増分取得 → HubSpot に upsert」というシンプルなパターンです。このパターンは Kintone に限らず、Salesforce / Notion / AirTable など多くの SaaS で流用できます。

従来は CRM ごとに連携設計を一から組む、という発想が一般的でした。実際には API の細部が違うだけで、設計の骨格は共通です。「LAST_SYNC を保存 → 差分取得 → upsert」の 3 ステップは、どの CRM でも変わりません。

私たち humbulls は、Kintone 実装を Salesforce / Notion / AirTable に横展開する場合の置き換えポイントを整理しています:

CRM 差分取得クエリ upsert キー 主な差分
Kintone 更新日時 > LAST_SYNC $id offset 10,000 上限 → $id 分割
Salesforce WHERE LastModifiedDate > LAST_SYNC (SOQL) Id Bulk API でレート制限緩和
Notion filter: { last_edited_time: { after: LAST_SYNC } } id (UUID) API レスポンスのページネーション (next_cursor)
AirTable filterByFormula: {Last Modified} > LAST_SYNC recordId API レート制限 5 req/秒で要注意

横展開の手順は「LAST_SYNC 保存ロジック → 差分取得クエリ → upsert ロジック」の順に置き換えていきます。HubSpot 側 (= 投入先) の upsert ロジックは共通なので、変更が必要なのは取得側だけです。

Claude Code に「Kintone の同期コードを Salesforce 向けに書き換えて」と頼むと、SOQL クエリと Bulk API の差を吸収したコードが 20 分で出てきます。同じ設計思想で複数 CRM の並行運用を組めば、運用の標準化も進みます。

注意点がひとつ。各 CRM の API レート制限はバラバラなので、横展開する前に必ず公式ドキュメントで確認してください。特に AirTable の 5 req/秒は実運用ではかなり厳しい制約なので、バッチサイズと sleep の調整が必須です。

🤖 AI で実行する — 使ったプロンプト / ワークフロー

目的: Kintone 同期コードを他 CRM (Salesforce / Notion / AirTable) に書き換える 所要時間: 20 分 ツール: Claude Code

プロンプト:

既存の Kintone → HubSpot 同期コードを Salesforce → HubSpot に書き換えてください。

【既存コード】
[Kintone 版の syncKintoneToHubSpot() を貼り付け]

【書き換え条件】
- 差分取得: SOQL の WHERE LastModifiedDate > LAST_SYNC
- 認証: Salesforce OAuth (= Bearer Token)
- Bulk API 2.0 を使ってレート制限を緩和
- upsert キー: Salesforce Id ↔ HubSpot custom property salesforce_id

【出力】 syncSalesforceToHubSpot() + fetchSalesforceIncremental() + 必須スコープ

運用 Tips: - Salesforce は API 制限が組織契約に紐づく (= Enterprise 以上で日次 15,000 req)、事前に容量確認 - Notion は API レート 3 req/秒なので、Utilities.sleep(400) を挟む実装が必要

📚 参考文献


サブ記事ナビゲーション

Pillar 記事「HubSpot Starter で Professional 級ダッシュボード」を起点に、シリーズ全 6 本でデータ層から既存 CRM 統合までを解説しています。

# タイトル 役割
#1 (Pillar) HubSpot Starter を Claude Code で限界突破!上位プランでしかできなかったレポートダッシュボードを再現 全体設計
#2 Apps Script で HubSpot ↔ Google Sheets を月額 $0 で自動同期 データ層
#3 Looker Studio で HubSpot ダッシュボードを 30 分で組む 可視化層
#4 Stage History を Apps Script 30 行で日次 Snapshot Professional 専用機能の代替
#5 月次コホート × 経過日数 × 勝率 — AI で 1 時間設計 分析フレームワーク
#6 (本記事) Kintone と HubSpot の並行運用 — 増分同期で破綻させない 既存 CRM 統合

humbulls の Growth Partner サービス では、Kintone を含む既存 CRM との並行運用設計から段階移行支援まで伴走しています。

Kintone × HubSpot 並行運用のテンプレと段階移行ロードマップを一括でダウンロードしたい方は、BtoB マーケ AI 活用ガイド もご活用ください。

こんな記事も読まれています