Contents
1. API 概要と認証方式
このセクションでは、Intercom と Slack が提供する REST/GraphQL エンドポイントと、2026 年時点で推奨されている認証フローを把握します。正しいバージョン管理とトークン取り扱いは、後続の実装全体に影響を与えるため必読です。
1‑1. Intercom API の最新情報
Intercom の公式ドキュメント(2026‑07 更新)によれば、REST API は v2.0 系列が唯一のサポート対象となっており、旧バージョン(v1.x)は完全に非推奨です。また、OAuth 2.0 の Authorization Code Grant フローは 2025 年末に廃止され、現在は Personal Access Token (PAT) のみが利用可能です。
| 項目 | 内容 |
|---|---|
| ベース URL | https://api.intercom.io(HTTPS 必須) |
| 推奨エンドポイント例 | /contacts, /conversations, /companies (すべて v2.0 に統合) |
| 認証方式 | Bearer Token:Authorization: Bearer <PAT> ヘッダーで送信 |
| トークン取得場所 | Intercom 管理画面 → Developer Hub → Personal Access Tokens |
| 有効期限・ローテーション | 無期限だが、セキュリティポリシーに従い 90 日ごと の再発行を推奨 |
注記:OAuth が廃止されたことに伴い、サードパーティ製の SDK でも内部的に PAT に置き換えるアップデートが必要です。
1‑2. Slack API の最新スコープと認証フロー
Slack は依然として OAuth 2.0 を採用していますが、2026 年版では「Bot Token Scopes」のみで十分に機能します。実装時に最小権限の原則(Principle of Least Privilege)を守るため、以下のスコープを最低限付与してください。
| スコープ | 主な利用シーン |
|---|---|
chat:write |
チャンネル・DM へのメッセージ送信 |
channels:read |
パブリックチャンネル情報取得 |
groups:read |
プライベートチャンネル(グループ)情報取得 |
im:read / mpim:read |
ダイレクトメッセージ・マルチパーティ DM の読み取り(通知対象の絞り込みに有用) |
reactions:write (任意) |
メッセージへのリアクション付与 |
認証フローは次の手順です。
- アプリ作成 → https://api.slack.com/apps
- OAuth & Permissions タブで上記スコープを設定し、
Redirect URLを登録 - インストール時に生成される Bot User OAuth Token(
xoxb-…)を環境変数で管理
Slack の公式ドキュメントは随時更新されますので、実装直前に必ず最新スコープ一覧を確認してください。
2. Webhook 設定とペイロード例
Intercom のイベントをリアルタイムで取得し、Slack に転送するための設定手順と、代表的な conversation.created と company.created ペイロード構造を示します。Webhook は署名検証が必須です。
2‑1. conversation.created の設定手順
Intercom の管理画面から対象イベントの Webhook を作成し、受信エンドポイントを自サーバーに指定します。
- Settings > Webhooks に移動
- 「Add webhook」 → イベント種別で
conversation.createdを選択 - 配信先 URL(例:
https://example.com/intercom/webhook)を入力 - 任意で 署名シークレット を生成し、保存
ポイント:テストペイロード送信機能で受信サーバーの 200 OK 応答が確認できたら設定完了です。
ペイロード例(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 |
{ "topic": "conversation.created", "id": "conv_12345", "created_at": 1730557200, "user_id": "usr_67890", "conversation_message": { "type": "admin", "body": "お問い合わせありがとうございます。" } } |
topic:イベント種別id:会話 ID(冪等性チェックに使用)conversation_message.body:本文テキスト
2‑2. company.created の設定手順
手順は上記と同様です。対象イベントだけを選択すれば、不要な通知を抑制できます。
ペイロード例(抜粋)
|
1 2 3 4 5 6 7 8 9 10 |
{ "topic": "company.created", "id": "comp_98765", "created_at": 1730557300, "name": "Acme Corp", "custom_attributes": { "plan": "enterprise" } } |
name:会社名custom_attributes:任意の顧客属性(後続ロジックで利用可能)
ベストプラクティス:受信サーバーは HMAC‑SHA256 による署名検証を必ず実装し、改ざんや偽装を防止してください。
3. ノーコード統合の選択肢(中立的に紹介)
プログラミング不要で Intercom → Slack の通知フローを構築したい場合、主に Zapier と Make (旧 Integromat) が利用されます。ここではそれぞれの設定手順と注意点だけを記載し、特定ベンダーへの過度な宣伝は控えています。
3‑1. Zapier を利用した構成
- Zapier のダッシュボードで 「Create Zap」
- Trigger に Intercom – New Conversation(または New Event)を選択し、PAT を入力
- Action として Slack – Send Channel Message を設定。メッセージテンプレートに
{{conversation_message.body}}などのプレースホルダーをマッピング - テスト実行で Slack に期待通りのテキストが届けば、Zap を有効化
留意点:無料プランは月間 100 タスクまで。大量通知が必要な場合は有料プランへの移行が必須です。
3‑2. Make (Integromat) を利用した構成
- Make の「Create a new scenario」画面で Intercom – Watch Events モジュールを追加
- 対象イベントとして
conversation.createdとcompany.createdを選択し、PAT で認証 - 次に Slack – Create a Message モジュールを接続。チャンネル ID とメッセージ本文(例:
{{payload.conversation_message.body}})をマッピング - Error handling タブでリトライ回数(最大 5 回)と指数バックオフの待機時間を設定し、レートリミット超過時に自動再試行できるようにする
留意点:無料プランは月間 1,000 操作まで。シナリオ実行頻度が高い場合は「フィルタ」モジュールで不要なイベントを除外し、コスト削減を図ります。
4. カスタム実装(Node.js と Python)
プログラムレベルの柔軟性が必要なケース向けに、Node.js と Python のサンプルコードと、実運用で推奨されるエラーハンドリング・リトライロジックを併せて提示します。
4‑1. Node.js 実装例と詳細なエラーハンドリング
|
1 2 3 |
# 必要パッケージ npm i express body-parser @slack/web-api dotenv axios bullmq |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
// index.js require('dotenv').config(); const express = require('express'); const bodyParser = require('body-parser'); const { WebClient } = require('@slack/web-api'); const crypto = require('crypto'); const { Queue, Worker } = require('bullmq'); const app = express(); app.use(bodyParser.json()); const slack = new WebClient(process.env.SLACK_BOT_TOKEN); const retryQueue = new Queue('intercom-retry', { connection: { host: process.env.REDIS_HOST, port: 6379 }, }); /** * Intercom の署名を検証する(HMAC‑SHA256) */ function verifySignature(req) { const signature = req.headers['intercom-signature']; if (!signature) return false; const payload = JSON.stringify(req.body); const hmac = crypto .createHmac('sha256', process.env.INTERCOM_WEBHOOK_SECRET) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(`sha256=${hmac}`), Buffer.from(signature) ); } /** * Slack へメッセージ送信(再試行ロジック付き) */ async function postToSlack(message) { try { await slack.chat.postMessage({ channel: process.env.SLACK_CHANNEL_ID, text: message, }); } catch (err) { // 429(レートリミット)や 5xx 系はキューに再投入 if (err.code === 'slack_webapi_platform_error' && err.data.retry_after) { const wait = Number(err.data.retry_after) * 1000; await retryQueue.add('postMessage', { message }, { delay: wait }); } else if (err.statusCode >= 500) { // 指数バックオフ(1,2,4,8,16 秒)で最大5回リトライ await retryQueue.add('postMessage', { message }, { attempts: 5, backoff: { type: 'exponential', delay: 1000, }, }); } else { console.error('Slack post failed irrecoverably:', err); } throw err; // 呼び出し側でもエラーログを残す } } /** * Webhook エンドポイント */ app.post('/intercom/webhook', async (req, res) => { if (!verifySignature(req)) { return res.status(400).send('Invalid signature'); } const { topic, conversation_message } = req.body; if (topic === 'conversation.created') { const text = `💬 新しい会話が届きました:\n${conversation_message.body}`; try { await postToSlack(text); res.status(200).send('ok'); } catch (_) { // キューに投入済みなので 500 を返すだけで十分 res.status(500).send('internal error'); } } else { // 対象外は即時 200 応答(冪等性確保のため) res.status(200).send('ignored'); } }); /** * キューからの再試行ワーカー */ new Worker( 'intercom-retry', async job => { await postToSlack(job.data.message); }, { connection: { host: process.env.REDIS_HOST, port: 6379 } } ); app.listen(process.env.PORT || 3000, () => console.log('Intercom‑Slack bridge listening') ); |
ポイント解説
| 項目 | 実装内容 |
|---|---|
| 署名検証 | crypto.timingSafeEqual で定数時間比較し、タイミング攻撃を防止 |
| 冪等性 | 未処理のイベントは 200 OK を返すだけに留め、重複通知はサーバ側で ID 管理(例:Redis Set)して防止 |
| リトライ戦略 | bullmq キューへ 指数バックオフ + jitter(内部実装)を設定し、レートリミットや 5xx エラーに自動再送 |
| ログ・モニタリング | console.error の代わりに Cloud Logging 等の構造化ログへ出力することが推奨 |
4‑2. Python 実装例と同等のハンドリング
|
1 2 |
pip install Flask slack_sdk python-dotenv redis rq |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# app.py import os, hmac, hashlib, json, time from flask import Flask, request, abort from slack_sdk import WebClient from dotenv import load_dotenv from rq import Queue, Retry from redis import Redis load_dotenv() app = Flask(__name__) slack = WebClient(token=os.getenv("SLACK_BOT_TOKEN")) redis_conn = Redis(host=os.getenv("REDIS_HOST"), port=6379) retry_q = Queue('intercom-retry', connection=redis_conn) def verify_signature(payload: bytes, signature: str) -> bool: secret = os.getenv("INTERCOM_WEBHOOK_SECRET").encode() expected = "sha256=" + hmac.new(secret, payload, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature) def post_to_slack(message: str): try: slack.chat_postMessage(channel=os.getenv("SLACK_CHANNEL_ID"), text=message) except Exception as e: # 429 や 5xx 系は RQ のリトライに委譲 raise e @app.route("/intercom/webhook", methods=["POST"]) def intercom_webhook(): sig = request.headers.get("Intercom-Signature") if not sig or not verify_signature(request.data, sig): abort(400, "Invalid signature") data = request.json if data["topic"] == "conversation.created": text = f"💬 New conversation:\n{data['conversation_message']['body']}" try: post_to_slack(text) except Exception as exc: # 429 の場合は `Retry-After` ヘッダーが無いので固定遅延 retry_q.enqueue( post_to_slack, args=(text,), retry=Retry(max=5, interval=[1, 2, 4, 8, 16]) ) # キュー投入後は 202 Accepted を返す return "", 202 return "", 200 if __name__ == "__main__": app.run(port=int(os.getenv("PORT", 5000))) |
- リトライ実装:
rqのRetryにより指数バックオフ(1 → 2 → 4 → 8 → 16 秒)を自動適用。 - エラーログは外部のロギングサービスへ出力することが望ましいです。
5. エラー処理・リトライ戦略とセキュリティベストプラクティス
5‑1. HTTP ステータス別ハンドリング表
| ステータス | 推奨アクション |
|---|---|
| 200‑299 | 正常完了 → 成功ログを残す |
| 400 | 署名不正・リクエストフォーマットエラー → ログのみ、再送は不要 |
| 401 / 403 | トークン無効またはスコープ不足 → 管理者へ即時アラート(PagerDuty 等) |
| 429 | レートリミット超過 → 指数バックオフ + jitter(最小 1 s、最大 30 s)で再試行 |
| 500‑504 | サーバー障害 → 最大 5 回 のリトライ後にキューへ永続化し、手動対応を促す |
5‑2. 冪等性と重複排除
- イベント ID(
id)をキーに Redis Set を使用し、受信済み ID が存在する場合は即座に200 OKを返すだけに留める。 - 同一会話が複数回通知されないよう、Slack 側でも thread_ts を利用してスレッド化すると管理しやすい。
5‑3. リトライ実装例(指数バックオフ+ジッター)
|
1 2 3 4 5 6 |
function exponentialBackoff(attempt) { const base = 1000; // 1 秒 const jitter = Math.random() * 500; // ±0.5 秒 return Math.pow(2, attempt) * base + jitter; } |
- Node.js:
setTimeout(() => fn(), exponentialBackoff(attempt)) - Python:
time.sleep(exponential_backoff(attempt))
5‑4. セキュリティ対策まとめ
| 項目 | 実装例 |
|---|---|
| 署名検証 | HMAC‑SHA256(Intercom)・Signing Secret(Slack Events API)を使用 |
| 最小権限 | Intercom は read:contacts, read:conversations; Slack は chat:write のみ付与 |
| シークレット管理 | Cloud Run → Secret Manager、Heroku → Config Vars、K8s → Secrets Store |
| トークンローテーション | 90 日ごとに自動生成スクリプトを走らせ、旧トークンは即削除 |
| 監査ログ | 受信・送信のリクエスト/レスポンスヘッダーを構造化ログで永続化(保持期間最低 90 日) |
6. テスト、デプロイ、運用チェックリスト
6‑1. ローカル開発と自動テスト
- ngrok でローカルサーバーを外部公開し、Intercom の「Test webhook」から実際のペイロードを送信
- ユニットテスト(Jest / pytest)に以下シナリオを必ず含める
- 正常系:有効署名 → Slack へメッセージが届くか
- 異常系①:無効署名 → 400 が返るか
- 異常系②:Slack API で 429 発生時にリトライロジックが作動するか
- CI/CD(GitHub Actions)例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Node uses: actions/setup-node@v3 with: { node-version: '20' } - run: npm ci && npm test - name: Set up Python uses: actions/setup-python@v4 with: { python-version: '3.11' } - run: pip install -r requirements.txt && pytest |
6‑2. デプロイ先別設定例
| 環境 | Dockerfile / Procfile の要点 |
|---|---|
| Heroku | web: node index.js または web: gunicorn app:app(Python) |
| Cloud Run | 上記 Dockerfile をビルドし、--allow-unauthenticated フラグで公開。環境変数は Secret Manager 経由で注入 |
| AWS Lambda (Node.js) | serverless.yml で HTTP API としてデプロイ、aws lambda invoke のリトライは Lambda の再試行設定でカバー |
6‑3. 運用時のモニタリングと定期メンテナンス
| 項目 | 推奨ツール・チェック項目 |
|---|---|
| ログ集約 | Cloud Logging / Papertrail → severity:ERROR が一定時間以上続くとアラート |
| パフォーマンス指標 | 受信レイテンシ < 500 ms、Slack 送信成功率 > 99% |
| トークン有効期限 | Cron ジョブで curl https://api.intercom.io/me を実行し 401 が返ったら自動通知 |
| 重複検知 | Redis Set のサイズを定期的にモニタリングし、閾値超過時は TTL 設定の見直し |
| 障害復旧手順 | 1) キュー残数確認 → 2) 手動リトライスクリプト実行 → 3) 必要ならデプロイロールバック |
終わりに
本ガイドは、2026 年時点での Intercom API(PAT) と Slack Bot Token を組み合わせて安全かつ拡張性のある通知基盤を構築するための包括的なロードマップです。
- API バージョン・認証方式は公式ドキュメントの更新に随時追従してください。
- エラーハンドリングとリトライは指数バックオフ+ジッターで実装し、レートリミットやサーバ障害への耐性を確保します。
- セキュリティは署名検証・最小権限・シークレット管理の 3 本柱を徹底し、外部連携リスクを最小化します。
この手順をベースに、組織固有の要件(多言語対応、カスタム属性マッピングなど)へ段階的に拡張すれば、長期にわたって安定した顧客サポートフローを実現できます。
本稿は 2026 年 7 月時点の公開情報に基づき執筆しています。API の破壊的変更や新しい認証方式が追加された場合は、速やかに公式リファレンスをご確認ください。