Contents
前提条件と法令チェック
このセクションでは、Mailchimp と Shopify の連携に必要なプラン要件と、日本国内外のデータ保護規制(GDPR・APPI)への対応ポイントを整理します。適切なプランとコンプライアンスが整っていないと、API 呼び出しでエラーが発生したり、法的リスクが生じる可能性があります。
必要プランの概要
| プラットフォーム | 推奨プラン | 主な理由 |
|---|---|---|
| Mailchimp | Standard 以上 | 顧客データの API アクセス、Webhook の利用が有料プランでのみ提供されます(※公式ドキュメント: https://mailchimp.com/developer/marketing/guides/access/)。 |
| Shopify | Basic または上位 | カスタムアプリ作成と Admin API トークン取得には有料プランが必須です(https://shopify.dev/docs/apps/authentication)。 |
GDPR と APPI に準拠した同意取得フロー
- 明示的なチェックボックスを設置し、マーケティング目的での利用に対する同意を取得。
- 同意内容は Shopify の顧客メタフィールド(例:
marketing_opt_in)に保存し、Mailchimp へ転送時に必ず付与。 - データ保持期間は APPI に基づき 3 年以内の削除 を自動化するバッチを用意。
ポイント:プライバシーポリシーへのリンクは、Shopify のチェックアウトページと Mailchimp のサインアップフォーム双方に必ず掲載してください(https://www.privacylaws.com/gdpr/)。
API キー取得と権限設定
Mailchimp の API キーは「Account → Extras → API keys」から生成できます。キー単位でスコープを付与できるため、必要な権限だけを明示的に選択してください。
権限(スコープ)について
| スコープ | 説明 | 必要な操作 |
|---|---|---|
marketing.read |
メーリングリストやオーディエンスの閲覧 | 顧客情報取得 |
ecommerce.write |
EC データ(注文・商品)の書き込み | 購入履歴の同期 |
※上記スコープは Mailchimp Marketing API v3.0 の現行ドキュメント(https://mailchimp.com/developer/marketing/api/)で確認できます。実装前に必ず最新のスコープ一覧を公式サイトでチェックしてください。
キーとスコープの安全な管理
- 取得した API キーは 環境変数(例:
MAILCHIMP_API_KEY)に保存し、コード内にハードコーディングしない。 .envファイルは Git の追跡対象外(.gitignoreに追加)。- CI/CD 環境ではシークレット管理サービス(GitHub Secrets, AWS Parameter Store 等)を利用する。
|
1 2 3 4 5 6 |
# .env 例 MAILCHIMP_API_KEY=your_api_key_here MAILCHIMP_STORE_ID=your_store_id SHOPIFY_TOKEN=shopify_access_token SHOPIFY_WEBHOOK_SECRET=webhook_hmac_secret |
Shopify 側の連携準備
Shopify ではカスタムアプリとノーコード連携ツール(例: Yoom)という二つの選択肢があります。以下ではそれぞれのメリット・デメリットを比較し、導入判断に必要な情報を提供します。
カスタムアプリ作成手順
| 手順 | 内容 |
|---|---|
| 1. アプリ有効化 | Apps → Develop apps for your store をオンにする。 |
| 2. アプリ生成 | Create an app → 名前を入力し、read_customers と write_orders(必要に応じて read_products)のスコープを付与。 |
| 3. トークン取得 | 作成したアプリの Admin API access token を発行し、環境変数 SHOPIFY_TOKEN に保存。 |
注意:スコープは最小権限の原則に従い、実際に使用するエンドポイントだけを許可してください(https://shopify.dev/docs/admin-api/access-scopes)。
ノーコード連携ツール(Yoom)選定基準
| 項目 | 評価ポイント |
|---|---|
| 初期費用 | 月額 $29〜 で開発工数が不要。 |
| カスタマイズ性 | 「Custom Mapping」機能で独自フィールドに対応可能。 |
| サポート体制 | 公式ドキュメントとチャットサポートが利用できる(https://yoom.io/docs)。 |
顧客データ同期スクリプト実装(Node.js & Python)
本章では、Shopify と Mailchimp の顧客情報・注文履歴を双方向に同期するサンプルコードと、エラーハンドリング・シークレット管理のベストプラクティスを示します。API バージョンは 2024‑07(Shopify)および v3.0(Mailchimp)を前提としています。
共通設計方針
- Bearer トークン認証で統一し、トークンの有効期限管理は不要です。
- 失敗したリクエストは ステータスコード別にリトライロジック(429 は指数バックオフ、5xx 系は一定待機)を実装。
- ログは JSON 形式で標準出力し、外部のログ集約サービス(Datadog, CloudWatch 等)へ流す想定。
Node.js 実装例
|
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 103 104 105 106 107 108 109 110 |
// sync_customers.mjs import fetch from 'node-fetch'; import { config } from 'dotenv'; config(); const { SHOPIFY_TOKEN, MAILCHIMP_API_KEY, MAILCHIMP_STORE_ID, } = process.env; // ---------- ユーティリティ ---------- function sleep(ms) => new Promise(r => setTimeout(r, ms)); async function requestWithRetry(url, options, attempt = 1) { try { const res = await fetch(url, options); if (res.ok) return res; // 429: Rate limit → exponential backoff if (res.status === 429 && attempt <= 5) { const wait = Math.pow(2, attempt) * 1000; // ms console.warn(`Rate limited. Retry #${attempt} after ${wait}ms`); await sleep(wait); return requestWithRetry(url, options, attempt + 1); } // 5xx: server error → simple retry if (res.status >= 500 && attempt <= 3) { const wait = attempt * 2000; console.warn(`Server error ${res.status}. Retry #${attempt} after ${wait}ms`); await sleep(wait); return requestWithRetry(url, options, attempt + 1); } // Other errors → throw const body = await res.text(); throw new Error(`HTTP ${res.status}: ${body}`); } catch (err) { if (attempt <= 3) { console.error(`Request failed (${err.message}). Retrying #${attempt}...`); await sleep(attempt * 2000); return requestWithRetry(url, options, attempt + 1); } throw err; } } // ---------- Shopify 顧客取得 ---------- async function fetchShopifyCustomers() { const url = 'https://your-store.myshopify.com/admin/api/2024-07/customers.json'; const res = await requestWithRetry(url, { headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN }, }); const data = await res.json(); return data.customers; } // ---------- Mailchimp 顧客 Upsert ---------- async function upsertMailchimpCustomer(customer) { const endpoint = `https://usX.api.mailchimp.com/3.0/ecommerce/stores/${MAILCHIMP_STORE_ID}/customers`; const payload = { email_address: customer.email, opt_in_status: true, first_name: customer.first_name ?? '', last_name: customer.last_name ?? '', address: { address1: customer.default_address?.address1 ?? '', city: customer.default_address?.city ?? '', province: customer.default_address?.province_code ?? '', postal_code: customer.default_address?.zip ?? '', country_code: customer.default_address?.country_code ?? '', }, tags: ['ShopifySync'], }; const options = { method: 'POST', headers: { Authorization: `Bearer ${MAILCHIMP_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }; try { await requestWithRetry(endpoint, options); } catch (err) { // 400 は既存顧客とみなして PATCH に切り替え if (err.message.includes('HTTP 400')) { const existingId = err.message.match(/"id":"([^"]+)"/)[1]; const patchUrl = `${endpoint}/${existingId}`; await requestWithRetry(patchUrl, { ...options, method: 'PATCH' }); } else { throw err; } } } // ---------- メイン処理 ---------- (async () => { try { const customers = await fetchShopifyCustomers(); for (const c of customers) { await upsertMailchimpCustomer(c); } console.info('✅ 顧客同期完了'); } catch (e) { console.error('❌ 同期失敗:', e); process.exit(1); } })(); |
Python 実装例
|
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 |
# sync_customers.py import os, json, time, logging, requests from dotenv import load_dotenv load_dotenv() SHOPIFY_TOKEN = os.getenv('SHOPIFY_TOKEN') MAILCHIMP_KEY = os.getenv('MAILCHIMP_API_KEY') STORE_ID = os.getenv('MAILCHIMP_STORE_ID') logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') def request_with_retry(method, url, **kwargs): """429/5xx 用指数バックオフリトライ""" max_attempts = 5 for attempt in range(1, max_attempts + 1): resp = requests.request(method, url, timeout=10, **kwargs) if resp.ok: return resp if resp.status_code == 429 and attempt < max_attempts: wait = (2 ** attempt) * 1.0 logging.warning(f'Rate limited ({resp.status_code}). retry {attempt}/{max_attempts} after {wait}s') time.sleep(wait) continue if 500 <= resp.status_code < 600 and attempt < max_attempts: wait = attempt * 2 logging.warning(f'Server error ({resp.status_code}). retry {attempt}/{max_attempts} after {wait}s') time.sleep(wait) continue # その他は例外送出 resp.raise_for_status() raise RuntimeError('Max retries exceeded') def fetch_shopify_customers(): url = 'https://your-store.myshopify.com/admin/api/2024-07/customers.json' headers = {'X-Shopify-Access-Token': SHOPIFY_TOKEN} resp = request_with_retry('GET', url, headers=headers) return resp.json()['customers'] def upsert_mailchimp_customer(customer): endpoint = f'https://usX.api.mailchimp.com/3.0/ecommerce/stores/{STORE_ID}/customers' payload = { 'email_address': customer['email'], 'opt_in_status': True, 'first_name': customer.get('first_name', ''), 'last_name': customer.get('last_name', ''), 'address': { 'address1': customer.get('default_address', {}).get('address1', ''), 'city': customer.get('default_address', {}).get('city', ''), 'province': customer.get('default_address', {}).get('province_code', ''), 'postal_code': customer.get('default_address', {}).get('zip', ''), 'country_code': customer.get('default_address', {}).get('country_code', '') }, 'tags': ['ShopifySync'] } headers = { 'Authorization': f'Bearer {MAILCHIMP_KEY}', 'Content-Type': 'application/json' } try: request_with_retry('POST', endpoint, json=payload, headers=headers) except requests.HTTPError as e: if e.response.status_code == 400: # 既存顧客 existing_id = e.response.json()['id'] patch_url = f'{endpoint}/{existing_id}' request_with_retry('PATCH', patch_url, json=payload, headers=headers) else: raise def main(): try: customers = fetch_shopify_customers() for cust in customers: upsert_mailchimp_customer(cust) logging.info('✅ 同期処理完了') except Exception as exc: logging.error(f'❌ 同期失敗: {exc}') raise SystemExit(1) if __name__ == '__main__': main() |
ポイント
-request_with_retryによって 429/5xx 系エラーは自動的に再試行。
- 環境変数を使用したシークレット管理でコードベースに認証情報が残らない。
Webhook とリアルタイム更新設定
Shopify のイベント(注文作成・顧客更新)を即座に Mailchimp へ反映させるための手順です。Webhook は 署名検証 を必ず行い、外部からの不正リクエストを防ぎます。
Shopify 側 Webhook 作成手順
- 管理画面 → Settings → Notifications → Webhooks → 「Create webhook」
- イベントは
orders/createとcustomers/updateを選択。 - フォーマットは JSON、配信先 URL は自社サーバのエンドポイント(例:
https://api.example.com/mailchimp/webhook) - 作成後に表示される Webhook ID と Shared secret(HMAC シークレット)をメモし、環境変数
SHOPIFY_WEBHOOK_SECRETに保存。
Mailchimp 受信用エンドポイント実装(Node.js/Express)
|
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 |
// webhook_receiver.mjs import express from 'express'; import crypto from 'crypto'; import fetch from 'node-fetch'; import { config } from 'dotenv'; config(); const { MAILCHIMP_API_KEY, MAILCHIMP_STORE_ID, SHOPIFY_WEBHOOK_SECRET, } = process.env; const app = express(); app.use(express.json()); // HMAC 検証ミドルウェア function verifyShopify(req, res, next) { const hmacHeader = req.get('X-Shopify-Hmac-Sha256'); const bodyString = JSON.stringify(req.body); const digest = crypto .createHmac('sha256', SHOPIFY_WEBHOOK_SECRET) .update(bodyString, 'utf8') .digest('base64'); if (crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(hmacHeader))) { return next(); } console.warn('Invalid Shopify HMAC'); res.status(401).send('Invalid signature'); } // 共通 Mailchimp 呼び出しユーティリティ async function callMailchimp(method, path, payload) { const url = `https://usX.api.mailchimp.com/3.0${path}`; const res = await fetch(url, { method, headers: { Authorization: `Bearer ${MAILCHIMP_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); if (!res.ok) { const txt = await res.text(); throw new Error(`Mailchimp ${res.status}: ${txt}`); } } // エンドポイント app.post('/mailchimp/webhook', verifyShopify, async (req, res) => { const topic = req.get('X-Shopify-Topic'); try { if (topic === 'orders/create') { const order = req.body; await callMailchimp('POST', `/ecommerce/stores/${MAILCHIMP_STORE_ID}/orders`, { id: String(order.id), customer: { email_address: order.email, first_name: order.customer?.first_name ?? '', last_name: order.customer?.last_name ?? '', }, currency_code: order.currency, total_price: order.total_price, }); } else if (topic === 'customers/update') { const cust = req.body; await callMailchimp('PATCH', `/ecommerce/stores/${MAILCHIMP_STORE_ID}/customers/${cust.id}`, { email_address: cust.email, first_name: cust.first_name, last_name: cust.last_name, }); } res.status(200).send('OK'); } catch (err) { console.error('Webhook processing error:', err); res.status(500).send('Server Error'); } }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => console.info(`🚀 Webhook listener running on ${PORT}`)); |
セキュリティ要点
- HMAC 検証は必ず実装し、失敗したリクエストは即座に 401 応答。
-X-Shopify-Topicヘッダーでイベント種別を判定し、不要な処理は排除する。
テスト・検証手順とエラーハンドリング方針
実装後の品質保証として、単体テスト → 統合テスト → 本番リハーサル の 3 段階で検証します。ここでは具体的な手順と、よくある API エラーへの対処方法をまとめます。
1. 単体テスト(ローカル)
| テスト項目 | 手法 | 期待結果 |
|---|---|---|
| 環境変数読み込み | dotenv のロード確認 (process.env が undefined でないか) |
正しくキーが取得できる |
| Shopify 顧客取得関数 | モックサーバ(nock / responses)で 200/404 をシミュレート | 成功時は配列、失敗時は例外スロー |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// jest の例 import { fetchShopifyCustomers } from './sync_customers.mjs'; import nock from 'nock'; test('fetchShopifyCustomers returns array', async () => { nock('https://your-store.myshopify.com') .get('/admin/api/2024-07/customers.json') .reply(200, { customers: [{ id: 1, email: 'a@b.c' }] }); const list = await fetchShopifyCustomers(); expect(Array.isArray(list)).toBe(true); expect(list[0].email).toBe('a@b.c'); }); |
2. 統合テスト(ステージング環境)
- サンドボックス顧客作成:Shopify の管理画面で
test@example.comを作成。 - 注文シミュレーション:金額 $5 のテストオーダーを作り、Webhook が正しく届くか確認(
curl -X POSTでローカル受信サーバへ送信)。 - 同期スクリプト実行:ステージングの API キー・トークンで
node sync_customers.mjsを走らせ、Mailchimp の対象リストに顧客が作成されていることを UI で確認。
3. 本番リハーサル(カナリアデプロイ)
- トラフィックの 10% にだけ新しい同期ロジックを適用し、エラー率とレートリミット状況をモニタリング。
- 問題が無ければ 100% デプロイ。
エラーコード別対策表
| ステータス | 発生原因例 | 推奨リトライ/対応 |
|---|---|---|
| 401 | API キー失効、スコープ不足 | キー再生成・スコープ確認(Mailchimp の API Keys ページ) |
| 403 | IP 制限やアカウント停止 | Mailchimp の IP Allowlist にサーバ IP を追加、またはサポートへ問い合わせ |
| 429 | レートリミット超過(Mailchimp は 10 req/sec、Shopify は 4 req/sec) | 指数バックオフで再試行、バッチ処理に分割 |
| 500‑504 | サービス側障害・ネットワークタイムアウト | 2〜3 回リトライ後、アラート送信(Slack/メール) |
データ保護チェックリスト(自動化推奨)
- [ ] 同意フラグ (
marketing_opt_in) がtrueのみ Mailchimp に送信 - [ ] 保存期間:3 年超過データは自動削除スクリプトで purge
- [ ] アクセスログ:全 API 呼び出しを JSON 形式で CloudWatch Logs に記録(保持期限 90 日)
- [ ] 監査レポート:月次で「同意取得率」「削除実行数」レポートを生成
|
1 2 3 |
# 削除バッチ例(Python) python delete_expired.py --days 1095 # 3 年 = 1095 日 |
自動化シナリオ例:購入後フォローアップ & カート放棄リマインダー
- 注文完了 (
orders/createWebhook) → Mailchimp に顧客・注文情報を送信し、タグPurchasedを付与。 - Mailchimp Automation で「タグが付いたら 24h 後にレビュー依頼メール」配信。
- カート放棄 (
checkout/createWebhook) → 同様にAbandonedCartタグ付与、48h 後に割引クーポン付きリマインダーを送信。
まとめ
| 項目 | 実装要点 |
|---|---|
| プランと法令 | Mailchimp Standard 以上・Shopify 有料プラン必須。GDPR と APPI に則り、チェックボックスで明示的同意を取得し、データ保持期間は 3 年以内に削除。 |
| API キー管理 | スコープは公式ドキュメントで確認し、marketing.read・ecommerce.write を付与。環境変数とシークレットストアで安全に保管。 |
| 連携方式選択 | カスタムアプリは最小権限で作成、ノーコードツールは導入コストと拡張性を比較して選定。 |
| 同期ロジック | Bearer 認証+指数バックオフリトライ実装の Node.js / Python サンプルをベースに、本番環境では CI/CD のシークレット管理とログ集約を組み合わせる。 |
| Webhook | Shopify で orders/create・customers/update を設定し、HMAC 検証付き Express エンドポイントでリアルタイム反映。 |
| テスト&運用 | 単体 → 統合 → カナリアデプロイの段階的検証、エラーコード別ハンドリングと 90 日ログ保持で障害を早期検知。 |
| 自動化 | 購入後フォローアップやカート放棄リマインダーは Mailchimp Automation と連携させ、マーケティング効果を最大化。 |
上記手順とベストプラクティスに従えば、2024 年時点の Mailchimp API と Shopify Admin API を安全かつ安定して統合でき、顧客情報・購買履歴の自動同期が実現します。ぜひ本稿を開発プロジェクトのチェックリストとして活用してください。