mcpField
W
whatsapp-templates

whatsapp-templates

github.com/zavudev营销与增长1265 次安装今天更新
代码仓库

关于 whatsapp-templates

面向使用 Zavu 的开发者,介绍 WhatsApp 模板消息的创建、提交、管理与发送,涵盖模板类别、渠道专属内容、Meta 审批及状态同步。

主要功能

  1. 01创建含变量和按钮的模板
  2. 02使用实用、营销和认证类别
  3. 03设置渠道专属消息正文
  4. 04提交模板供 Meta 审批
  5. 05同步 WhatsApp 模板状态与类别
  6. 06发送带变量值的已批准模板

适用场景

  1. 01发送订单和配送更新
  2. 02发送促销或群发消息
  3. 03发送 OTP 和验证码
  4. 04同步从 Meta 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推广内容促销、优惠、产品公告
AUTHENTICATIONOTP/验证码登录验证码、双重身份验证、密码重置

创建模板

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 通知。即使批准状态没有变化,该 Webhook 也会触发,因此重新分类事件中的 previousStatus 和 currentStatus 会相同——应根据 category 分支处理,而不只是检查 currentStatus:

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 上被拒绝/禁用(这些模板永远不会被导入)。
  • errors 非空但响应为 200,表示一个账户同步失败;其余账户仍已同步。
  • 如果项目中没有发送方关联 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),且在每个项目中必须唯一:创建第二个同名模板会返回 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 通常会在几分钟到几小时内完成审核,但最长可能需要 24h
  • 被拒绝的模板可以编辑后重新提交

评论

支持 Markdown