跳转到正文
OmniChannels.ai

WhatsApp消息API发送示例:cURL、Node、Python、PHP

如何用API发送WhatsApp消息:提供可运行的cURL、Node.js、Python和PHP示例,涵盖模板消息与会话消息、送达状态跟踪以及常见错误处理,方便开发者快速接入。

更新于 10 分钟阅读

作者: OmniChannels.ai Editorial Team · 审阅: BroadNet Messaging Solutions Team

集成

简短回答

使用OmniChannels.ai API,所有WhatsApp消息都通过同一个端点POST https://api.broadnet.me/whatsapp/message/sendbulk发送,请求采用multipart表单数据,并带有Authorization: Bearer请求头。messageType字段决定负载类型:TEMPLATE用于已获批的模板,开启对话或在24小时窗口之外发送时必须使用;窗口内的回复则使用TEXT、IMAGE或INTERACTIVE。

本页内容
  1. 如何通过API发送WhatsApp消息?
  2. 应该发送模板消息还是会话消息?
  3. 如何发送已获批的模板?
  4. 哪些请求字段最重要?
  5. 如何发送带按钮或验证码的模板?
  6. 如何在客户服务窗口内回复?
  7. 如何跟踪送达?
  8. 需要处理哪些错误?
  9. 实战示例:订单通知服务
  10. 常见错误有哪些?
  11. OmniChannels.ai 如何提供帮助

要点速览

  • 所有WhatsApp消息都通过POST /whatsapp/message/sendbulk发送;messageType(TEMPLATE、TEXT、IMAGE、INTERACTIVE等)决定哪些字段为必填。
  • 请求是multipart/form-data,而不是JSON,因此Python需要使用files=,PHP需要使用数组作为请求体,以避免被URL编码。
  • 在24小时客户服务窗口之外,只能发送已获批的模板;窗口内则可以发送自由格式的文本、媒体和互动消息。
  • 该调用会把消息排入队列并立即返回batchId;每个收件人的状态会推送到您的Webhook,也可以通过bulk-insights端点获取。
  • 该API不接受幂等键,所以请在发送前先记录每次发送,超时后检查状态,而不是重发。

本指南面向将WhatsApp发送功能集成到应用中的开发者,提供cURL、Node.js、Python和PHP的可运行请求,说明模板消息与会话消息的区别,并涵盖状态跟踪和错误处理。每个端点和字段都来自我们的WhatsApp API参考和OpenAPI规范。

如何通过API发送WhatsApp消息?

您向https://api.broadnet.me/whatsapp/message/sendbulk发起一次带身份验证的POST请求,并附上表单字段。messageType字段选择消息类型,响应则确认消息已排入队列。

项目 值
基础URL https://api.broadnet.me(无/v1前缀)
端点 POST /whatsapp/message/sendbulk
鉴权 Authorization: Bearer $BROADNET_TOKEN
内容类型 multipart/form-data(由您的HTTP客户端自动设置)
响应 带有batchId的JSON批次摘要

测试凭证在提交简短的访问申请后发放,测试发送只发往您自己已验证的号码。请参阅快速入门和鉴权页面。请仅在服务器端保存token。

应该发送模板消息还是会话消息?

要开启对话或在窗口之外发送,就发送模板;客户在过去24小时内给您发过消息时,就发送会话消息。这条规则由Meta制定,而不是我们。

模板消息 会话消息
messageType TEMPLATE TEXT、IMAGE、VIDEO、AUDIO、DOCUMENT、INTERACTIVE等
何时允许 模板获批后随时可用 仅在客户服务窗口开启期间
是否需要审核 需要,由Meta审核 不需要
典型用途 订单更新、验证码、提醒、营销活动 客服回复、问题解答

Meta按送达消息的类别收费,其关于服务消息和实用型(Utility)模板的规则已于2026年10月1日变更。我们的窗口指南介绍了运作机制,价格页面和计算器则涵盖费用。

如何发送已获批的模板?

设置messageType=TEMPLATE,指明模板名称及其语言代码,并传入变量值。设置batchId=0以创建新批次。

curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F campaignName=order-updates \
  -F destinationNo=971500000000 \
  -F messageType=TEMPLATE \
  -F templateName=order_update \
  -F templateLanguage=en_US \
  -F bodyTemplateVariables=INV-400
// Node.js 18+(内置fetch和FormData)
const form = new FormData();
form.append("batchId", "0");
form.append("campaignName", "order-updates");
form.append("destinationNo", "971500000000");
form.append("messageType", "TEMPLATE");
form.append("templateName", "order_update");
form.append("templateLanguage", "en_US");
form.append("bodyTemplateVariables", "INV-400");

const res = await fetch("https://api.broadnet.me/whatsapp/message/sendbulk", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.BROADNET_TOKEN}` }, // fetch会自动设置multipart边界
  body: form,
});
console.log(await res.json());
import os
import requests

fields = {
    "batchId": "0",
    "campaignName": "order-updates",
    "destinationNo": "971500000000",
    "messageType": "TEMPLATE",
    "templateName": "order_update",
    "templateLanguage": "en_US",
    "bodyTemplateVariables": "INV-400",
}
# 把每个字段作为multipart部分发送(files=),而不是URL编码(data=)。
r = requests.post(
    "https://api.broadnet.me/whatsapp/message/sendbulk",
    headers={"Authorization": f"Bearer {os.environ['BROADNET_TOKEN']}"},
    files={k: (None, v) for k, v in fields.items()},
    timeout=30,
)
print(r.json())
<?php
$ch = curl_init("https://api.broadnet.me/whatsapp/message/sendbulk");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("BROADNET_TOKEN")],
    // 传入数组(而不是字符串)会让cURL发送multipart/form-data。
    CURLOPT_POSTFIELDS => [
        "batchId" => "0",
        "campaignName" => "order-updates",
        "destinationNo" => "971500000000",
        "messageType" => "TEMPLATE",
        "templateName" => "order_update",
        "templateLanguage" => "en_US",
        "bodyTemplateVariables" => "INV-400",
    ],
]);
echo curl_exec($ch);
curl_close($ch);

目的地号码是国家区号加号码,仅含数字。调用会立即返回一份批次摘要:

{
  "totalFiles": 1,
  "totalSuccess": 1,
  "totalFailed": 0,
  "totalDuplicates": 0,
  "batchId": "2609091405000000001",
  "fileResults": [
    {
      "fileName": "direct",
      "totalRecords": 1,
      "successCount": 1,
      "failedRecipients": [],
      "duplicateRecipients": []
    }
  ],
  "message": "Messages queued for delivery"
}

请把batchId与您自己的记录一起保存。“已排队”不等于“已送达”。

哪些请求字段最重要?

哪些字段必填取决于messageType。以下是常用字段。

字段 是否必填 说明
messageType 是 TEXT、IMAGE、VIDEO、AUDIO、DOCUMENT、STICKER、LOCATION、CONTACTS、TEMPLATE、INTERACTIVE、REACTION或FLOW
destinationNo或destinationFiles 二选一 以逗号、分号或换行分隔的号码;或TXT、CSV、XLSX文件
batchId 否 0表示新建批次
campaignName 否 用于报表的自定义标签
allowDuplicate 否 false会移除重复的收件人
templateName、templateLanguage TEMPLATE时必填 已获批的模板名称和语言代码,如en_US或ar
bodyTemplateVariables 视模板而定 正文变量值;命名参数以JSON传递
buttonsTemplateVariable 视模板而定 动态按钮值:URL后缀、优惠码或验证码
content TEXT时必填 文本正文,最多4,096个字符,或媒体的说明文字
mediaUrl 视类型而定 媒体的公开HTTPS URL
interactivePayload INTERACTIVE时必填 Meta的互动对象,作为JSON字符串

定时发送使用scheduled、scheduleTime和gmt,delay用于节流发送。完整列表见WhatsApp API参考。

如何发送带按钮或验证码的模板?

把按钮值放进buttonsTemplateVariable。对于带复制验证码按钮的身份验证模板,请在正文字段和按钮字段中都传入验证码。

curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F destinationNo=971500000000 \
  -F messageType=TEMPLATE \
  -F templateName=login_code \
  -F templateLanguage=en_US \
  -F bodyTemplateVariables=482913 \
  -F buttonsTemplateVariable=482913

这里的login_code是您自己已获批身份验证模板的占位符。对于多个按钮,buttonsTemplateVariable接受JSON数组。请在服务器端生成验证码,带有效期以哈希形式存储,并且绝不要写入日志。我们的验证码指南介绍了一键、零点击和复制验证码的投递方式。

如何在客户服务窗口内回复?

使用会话消息类型。纯文本回复需要messageType=TEXT和content。

curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F destinationNo=971500000000 \
  -F messageType=TEXT \
  -F campaignName=support \
  -F "content=Your order has shipped. Reply here if you need anything."

如需回复按钮,请发送messageType=INTERACTIVE以及遵循Meta互动消息格式的interactivePayload。

curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F destinationNo=971500000000 \
  -F messageType=INTERACTIVE \
  -F campaignName=support \
  -F 'interactivePayload={"type":"button","body":{"text":"Confirm your delivery slot?"},"action":{"buttons":[{"type":"reply","reply":{"id":"slot_ok","title":"Confirm"}},{"type":"reply","reply":{"id":"slot_change","title":"Change"}}]}}'

要将收到的消息标记为已读,并可选地显示“正在输入”指示,请用JSON调用POST /whatsapp/message/read:

curl -X POST https://api.broadnet.me/whatsapp/message/read \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "messageId": "wamid.HBgMOTcxNTAwMDAwMDAwFQIAEhgg", "typing": true }'

设计方面的建议,请参阅我们的互动消息指南。

如何跟踪送达?

状态会推送到您账户上配置的Webhook,也可以按批次读取。每个收件人依次经过SUBMITTED、SENT、DELIVERED和READ,或以FAILED或REJECTED结束并附带原因。

curl "https://api.broadnet.me/whatsapp/message/bulk-insights?batch_Id=2609091405000000001&status=FAILED&page=0&size=50" \
  -H "Authorization: Bearer $BROADNET_TOKEN"

batch_Id为必填;status、destination、search、page和size为可选,其中page从零开始。请把轮询作为错过事件时的兜底手段,并按照我们的Webhook指南构建您的接收端。批次还可以通过参考文档中的批次端点暂停、恢复和中止。

需要处理哪些错误?

先看HTTP状态,再看错误响应体。API以五字段的JSON响应体抛出错误,WhatsApp特有的失败则可能来自Meta。

状态 含义 操作
400 校验错误,例如缺少类型特有的字段或目的地无效 修正请求;不要原样重试
401 / 403 token缺失或无效,或操作不被允许 修正token,或请我们启用该操作
404 找不到模板或发件人 检查模板名称和语言
413 上传的文件过大 缩小文件
429 速率限制 等待Retry-After
502 Meta拒绝了该调用;message中带有Meta的错误码和文本 阅读内容;仅在属于暂时性错误时重试
500、503、504 服务端或上游故障 采用指数退避和抖动重试

我们的错误码指南说明了常见的Meta错误码。由于没有幂等键,请遵循错误与限制中的重复发送防护步骤:先写入待处理记录,保存返回的batchId,并在超时后先检查状态再重发。

实战示例:订单通知服务

“Souq Rafiq”(虚构)是阿联酋的一家零售商,订单发货时会发送order_update模板。

  1. 订单服务写入一行键为order-8842-shipped、状态为pending的记录。
  2. 它以模板和作为bodyTemplateVariables的订单号调用sendbulk。
  3. 返回200时,它保存batchId,并把该行标记为accepted。
  4. Webhook处理程序把该行更新为DELIVERED或READ,或带原因的FAILED。
  5. 如果客户回复,客服工具在窗口内使用TEXT会话消息。
  6. 夜间任务读取昨日批次的bulk-insights,并对账任何缺失的状态。

常见错误有哪些?

  • **发送JSON。**WhatsApp发送端点是multipart;Content-Type: application/json不起作用。
  • **Python用data=**而不是files=,会对表单进行URL编码。
  • **在窗口之外发送自由格式文本,**会失败。请使用模板。
  • **语言代码错误,**例如用en而不是模板的en_US。
  • **把200当作已送达。**它只表示已排队。
  • **超时后盲目重试,**可能让客户收到两次消息。
  • **token出现在客户端代码中。**请仅从后端调用API。
  • **局限:**负载细节因模板而异。首次个性化营销活动的变量布局,请与我们的团队确认。

OmniChannels.ai 如何提供帮助

BroadNet是Meta官方的Business Solution Provider,WhatsApp Business API的开通使用Meta Embedded Signup;我们的团队与您一起准备注册,审批仍由Meta决定。REST API配有OpenAPI 3.1规范和TypeScript SDK @broadnet/cpaas-sdk,各渠道的送达状态统一为同一种格式。请从WhatsApp API文档或快速入门开始。

常见问题

发送WhatsApp消息应该用JSON还是表单数据?

表单数据。WhatsApp发送端点POST /whatsapp/message/sendbulk接受multipart/form-data,每个字段是一个表单部分。短信的单条发送则使用JSON,这是常见的混淆来源。在Python中请通过files=而不是data=发送字段,在PHP中请将数组作为请求体传入,这样cURL会构造multipart。

不使用模板可以发送WhatsApp消息吗?

只有在客户服务窗口内才可以。客户给您发消息后,会开启24小时窗口,您可以用文本、媒体或互动消息回复。窗口之外,或要开启一段对话时,必须发送已获批的模板。API会执行Meta的规则,向已关闭的窗口发送自由格式消息会失败。

如何知道消息是否已送达?

发送调用只确认消息已排入队列,并返回一个batchId。每个收件人的状态依次经过SUBMITTED、SENT、DELIVERED和READ,或以FAILED、REJECTED结束。它们会推送到您的Webhook,您也可以通过bulk-insights端点按批次读取。

可以在一个请求里发送给多个收件人吗?

可以。destinationNo接受以逗号、分号或换行分隔的多个号码,destinationFiles接受TXT、CSV或XLSX文件。如果模板需要为每个收件人使用不同的变量,请上传每个收件人一行的个性化文件。将allowDuplicate设为false可以移除重复号码。

有防止重复发送的幂等键吗?

没有。该API不接受幂等键。如果请求超时,您无法从客户端判断它是否已被接受。请在调用之前先在自己的数据库中记录每次发送,保存返回的batchId,并在任何重试之前检查状态或批次列表。

发送之前需要事先同意吗?

需要。Meta的政策要求每位收件人都已同意接收来自您企业的消息,模板在使用前必须获批。API不会替您检查是否有用户同意。请在源头收集事先同意、保存证据,并屏蔽任何已退订的用户。

资料来源

  1. WhatsApp Business消息政策
  2. WhatsApp Business平台定价——Meta for Developers
  3. RFC 7578:表单值的返回:multipart/form-data