WhatsApp消息API发送示例:cURL、Node、Python、PHP
如何用API发送WhatsApp消息:提供可运行的cURL、Node.js、Python和PHP示例,涵盖模板消息与会话消息、送达状态跟踪以及常见错误处理,方便开发者快速接入。
作者: 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。
本页内容
要点速览
- 所有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模板。
- 订单服务写入一行键为
order-8842-shipped、状态为pending的记录。 - 它以模板和作为
bodyTemplateVariables的订单号调用sendbulk。 - 返回
200时,它保存batchId,并把该行标记为accepted。 - Webhook处理程序把该行更新为
DELIVERED或READ,或带原因的FAILED。 - 如果客户回复,客服工具在窗口内使用
TEXT会话消息。 - 夜间任务读取昨日批次的
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不会替您检查是否有用户同意。请在源头收集事先同意、保存证据,并屏蔽任何已退订的用户。
资料来源
继续阅读
更多指南集成
WhatsApp Webhook指南:事件、安全与重试
WhatsApp Webhook的工作方式:消息与状态事件、可靠的处理程序、Meta签名校验、重试机制、幂等处理,以及事件丢失时的恢复办法,帮助开发者稳定接收送达状态。
10 分钟阅读
集成
短信API发送示例:Python、Node.js、PHP与cURL
用API发送短信的Python、Node.js、PHP和cURL示例:单条与批量发送、Unicode与阿拉伯语、分段计数、送达状态Webhook以及错误处理,开发者可直接复制使用。
10 分钟阅读
WhatsApp
WhatsApp验证码与短信验证码:成本、送达与回退
WhatsApp验证码与短信验证码对比:成本模型、送达率、用户体验、身份验证模板、WhatsApp到短信的回退设计,以及防刷与反欺诈控制,帮助出海产品选对验证渠道。
10 分钟阅读
