whatsapp-templates について
Zavu を使う開発者向けに、WhatsApp テンプレートメッセージの作成、申請、管理、送信方法を説明します。カテゴリ、チャネル別本文、Meta の承認、状態同期も扱います。
主な機能
- 01変数やボタン付きテンプレートの作成
- 02ユーティリティ、マーケティング、認証カテゴリの利用
- 03チャネル別メッセージ本文の設定
- 04Meta へのテンプレート審査申請
- 05WhatsApp の状態とカテゴリの同期
- 06承認済みテンプレートへの変数指定と送信
活用シーン
- 01注文や配送状況の通知
- 02販促や一斉配信
- 03OTP や認証コードの送信
- 04Meta Business Manager で作成したテンプレートの同期
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ zavudev/ zavu-skills --skill whatsapp-templatesインストール後、Claude Code で/whatsapp-templatesを入力して読み込まれたか確認します。
詳細
WhatsApp テンプレート
使用するタイミング
WhatsApp テンプレートメッセージを作成、管理、または送信するコードを構築するときに、このスキルを使用します。24 時間のウィンドウ外で会話を開始するには、テンプレートが必要です。
テンプレートが必要な場合
Has user messaged you in the last 24 hours?
-> YES: Send free-form message (text, image, buttons, etc.)
-> NO: Must use template message
Need to send bulk/broadcast messages?
-> YES: Use template (recommended for consistency)
Want proactive outbound notifications?
-> YES: Must use template
テンプレートのカテゴリ
| カテゴリ | 使用例 | 例 |
|---|---|---|
UTILITY | 取引に関する更新 | 注文確認、配送状況の更新、予約のリマインダー |
MARKETING | プロモーションコンテンツ | セール、オファー、製品のお知らせ |
AUTHENTICATION | OTP/認証コード | ログインコード、2FA、パスワードのリセット |
テンプレートの作成
const template = await zavu.templates.create({
name: "order_confirmation",
language: "en",
body: "Hi {{1}}, your order {{2}} has been confirmed and will ship within 24 hours.",
whatsappCategory: "UTILITY",
variables: ["customer_name", "order_id"],
});
console.log(template.id); // tpl_xxx
Python:
template = zavu.templates.create(
name="order_confirmation",
language="en",
body="Hi {{1}}, your order {{2}} has been confirmed and will ship within 24 hours.",
whatsapp_category="UTILITY",
variables=["customer_name", "order_id"],
)
Go:
template, err := client.Templates.Create(context.TODO(), zavudev.TemplateCreateParams{
Name: zavudev.String("order_confirmation"),
Language: zavudev.String("en"),
Body: zavudev.String("Hi {{1}}, your order {{2}} has been confirmed and will ship within 24 hours."),
WhatsappCategory: zavudev.String("UTILITY"),
Variables: []string{"customer_name", "order_id"},
})
Ruby:
template = client.templates.create(
name: "order_confirmation",
language: "en",
body: "Hi {{1}}, your order {{2}} has been confirmed and will ship within 24 hours.",
whatsapp_category: "UTILITY",
variables: ["customer_name", "order_id"],
)
PHP:
$template = $client->templates->create([
'name' => 'order_confirmation',
'language' => 'en',
'body' => 'Hi {{1}}, your order {{2}} has been confirmed and will ship within 24 hours.',
'whatsappCategory' => 'UTILITY',
'variables' => ['customer_name', 'order_id'],
]);
チャネル固有の本文
テンプレートでは、チャネルごとに異なる本文を設定できます。デフォルトの body は WhatsApp で使用されます。チャネル固有の本文が設定されていない場合、SMS、Telegram、Instagram では body が使用されます。
const template = await zavu.templates.create({
name: "order_confirmation",
language: "en",
body: "Hi {{1}}, your order {{2}} has been confirmed and will ship within 24 hours.",
smsBody: "Order {{2}} confirmed. Ships in 24h.", // SMS fallback
telegramBody: "✅ Order {{2}} confirmed for {{1}}.", // Telegram-specific
instagramBody: "Order {{2}} confirmed!", // Instagram-specific
whatsappCategory: "UTILITY",
variables: ["customer_name", "order_id"],
});
whatsappCategory は WhatsApp の body にのみ適用されます。チャネル固有の本文にはカテゴリは必要ありません。
ボタン付きテンプレート
// Quick reply buttons
const template = await zavu.templates.create({
name: "feedback_request",
language: "en",
body: "Hi {{1}}, how was your experience with order {{2}}?",
whatsappCategory: "MARKETING",
variables: ["customer_name", "order_id"],
buttons: [
{ type: "quick_reply", text: "Great!" },
{ type: "quick_reply", text: "Could be better" },
],
});
// URL button
const template = await zavu.templates.create({
name: "track_order",
language: "en",
body: "Hi {{1}}, your order {{2}} has shipped!",
whatsappCategory: "UTILITY",
variables: ["customer_name", "order_id"],
buttons: [
{ type: "url", text: "Track Order", url: "https://example.com/track/{{1}}" },
],
});
// Phone button
const template = await zavu.templates.create({
name: "contact_support",
language: "en",
body: "Need help? Call our support team.",
whatsappCategory: "UTILITY",
buttons: [
{ type: "phone", text: "Call Support", phoneNumber: "+14155551234" },
],
});
// Contact info request button — asks the recipient to share their phone number.
// Label is fixed by WhatsApp ("Share Contact Info"), so `text` is not required.
// Useful for contacts who adopted a WhatsApp username (known only by BSUID).
const template = await zavu.templates.create({
name: "callback_request",
language: "en",
body: "Hi {{1}}, we need a phone number to schedule your callback.",
whatsappCategory: "UTILITY",
variables: ["customer_name"],
buttons: [
{ type: "request_contact_info" },
],
});
OTP 認証テンプレート
// Copy code button
const template = await zavu.templates.create({
name: "login_otp",
language: "en",
body: "Your verification code is {{1}}. Do not share this code.",
whatsappCategory: "AUTHENTICATION",
variables: ["otp_code"],
addSecurityRecommendation: true,
codeExpirationMinutes: 5,
buttons: [
{ type: "otp", text: "Copy Code", otpType: "COPY_CODE" },
],
});
// One-tap autofill (Android)
const template = await zavu.templates.create({
name: "login_otp_autofill",
language: "en",
body: "Your verification code is {{1}}.",
whatsappCategory: "AUTHENTICATION",
variables: ["otp_code"],
addSecurityRecommendation: true,
codeExpirationMinutes: 10,
buttons: [
{
type: "otp",
text: "Autofill",
otpType: "ONE_TAP",
packageName: "com.example.app",
signatureHash: "abc123hash",
},
],
});
Meta の承認に申請する
テンプレートは使用前に Meta の承認が必要です。
// Submit template for review
const submitted = await zavu.templates.submit({
templateId: "tpl_abc123",
senderId: "snd_abc123",
category: "UTILITY",
});
console.log(submitted.status); // "pending"
Webhook(template.status_changed イベント)またはポーリングで承認状況を確認します。
const template = await zavu.templates.get({ templateId: "tpl_abc123" });
console.log(template.status); // draft | pending | approved | rejected
カテゴリを決めるのは Meta です
申請するカテゴリはリクエストです。適用されるカテゴリは Meta が決定し、変更されることがあります。承認時、またはすでに公開済みのテンプレートで数か月後に変更されることもあり、特に UTILITY から MARKETING への変更がよくあります。この変更によって、そのテンプレートを使ったすべての送信の料金が変わります。
template.category はMeta が現在割り当てているカテゴリを返すため、申請時のカテゴリが今も有効だと思い込まず、値を読み取ってください。
const template = await zavu.templates.get({ templateId: "tpl_abc123" });
console.log(template.category); // UTILITY | MARKETING | AUTHENTICATION
カテゴリの再分類は、data.category を含む template.status_changed Webhook として届きます。承認ステータスが変わらなくても発火するため、再分類時のイベントでは previousStatus と currentStatus が同じ値になります。currentStatus だけでなく、category を基準に分岐してください。
if (event.type === "template.status_changed") {
const { templateId, currentStatus, category } = event.data;
if (category !== myStoredCategory(templateId)) {
// Meta moved the billing category. Reprice before the next send.
}
}
監視を開始する前に変更されたテンプレートの状態を同期するには、POST /v1/templates/sync を実行してください。詳細は以下をご覧ください。
WhatsApp からテンプレートを同期する
Zavu 以外(Meta Business Manager または別のツール)で作成されたテンプレートは、インポートするまで Zavu に存在しません。template.status_changed Webhook を受信できなかった場合、テンプレートはいつまでも pending のままになります。また、監視が行われていない間に Meta がカテゴリを再分類したテンプレートは、以前のカテゴリを報告し続けます。POST /v1/templates/sync を実行すると、これらの問題をすべて解決できます。Zavu にない Meta のテンプレートをインポート(または同名の既存テンプレートにリンク)し、既存テンプレートのステータスとカテゴリの両方を更新します。
カテゴリは変わったもののステータスは変わっていないテンプレートも、updated にカウントされます。このケースに対処することが、この操作を実行する目的のひとつです。
SDK ではまだ生成されていないため、REST 経由で呼び出してください。
# Sync every WhatsApp sender in the project
curl -X POST https://api.zavu.dev/v1/templates/sync \
-H "Authorization: Bearer $ZAVU_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
# Or scope it to one sender's WhatsApp Business Account
curl -X POST https://api.zavu.dev/v1/templates/sync \
-H "Authorization: Bearer $ZAVU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"senderId": "snd_abc123"}'
{
"accountsSynced": 1,
"imported": 2,
"linked": 1,
"updated": 3,
"skipped": 12,
"errors": []
}
- この呼び出しは同期処理です。Meta からの応答を待つため、アカウントごとに数秒かかることがあります。結果を確認するには、直後にテンプレート一覧を取得してください。
skippedは処理対象外となった Meta のテンプレート数です。すでにリンク済みのもの、または Meta 側で拒否/無効化されているものが該当します(これらはインポートされません)。- HTTP 200 でも
errorsが空でない場合は、1 つのアカウントでエラーが発生しています。その他のアカウントの同期は完了しています。 - プロジェクト内の送信元に WhatsApp Business Account がない場合は、400 を返します。
テンプレートメッセージの送信
await zavu.messages.send({
to: "+14155551234",
messageType: "template",
content: {
templateId: "tpl_abc123",
templateVariables: { "1": "John", "2": "ORD-12345" },
},
});
Python:
zavu.messages.send(
to="+14155551234",
message_type="template",
content={
"templateId": "tpl_abc123",
"templateVariables": {"1": "John", "2": "ORD-12345"},
},
)
テンプレートのライフサイクル
draft -> pending (submitted to Meta) -> approved (ready to use)
-> rejected (edit and resubmit)
その他の操作
// List templates
const templates = await zavu.templates.list({ limit: 50 });
for (const tpl of templates.items) {
console.log(tpl.id, tpl.name, tpl.status);
}
// Get template
const tpl = await zavu.templates.get({ templateId: "tpl_abc123" });
// Delete template (draft only)
await zavu.templates.delete({ templateId: "tpl_abc123" });
制約
- テンプレート名:小文字とアンダースコアのみを使用してください(例:
order_confirmation)。プロジェクト内で一意である必要があります。同じ名前のテンプレートを2つ目に作成すると、409 conflictが返されます - 変数には位置形式を使用します:
{{1}}、{{2}}、{{3}} - WhatsApp の変数値には、改行、タブ、または4つを超える連続したスペースを含められません。Meta は配信時にこれらを拒否するため、Zavu は送信を拒否し、
400 invalid_requestを返してdetails.fieldに該当する値を示します。送信前に(例:·に)置き換えてください。SMS と Telegram のテンプレートでは使用できます - テンプレートあたりボタンは最大3つです
- ボタンのテキスト:最大25文字(
request_contact_infoでは使用されません。WhatsApp がラベルを固定します) - OTP の
ONE_TAPには Android のpackageNameとsignatureHashが必要です addSecurityRecommendationは AUTHENTICATION テンプレートでのみ使用できますcodeExpirationMinutes:1~90。AUTHENTICATION でのみ使用できます- Meta の承認には通常、数分から数時間かかりますが、最大24時間かかる場合があります
- 拒否されたテンプレートは編集して再送信できます