---
title: "WhatsApp消息API发送示例：cURL、Node、Python、PHP"
description: "如何用API发送WhatsApp消息：提供可运行的cURL、Node.js、Python和PHP示例，涵盖模板消息与会话消息、送达状态跟踪以及常见错误处理，方便开发者快速接入。"
canonical: "https://omnichannels.ai/zh/blog/send-whatsapp-message-api-examples/"
language: "zh"
updated: "2026-10-16"
publisher: "BroadNet Technologies"
brand: "OmniChannels.ai"
category: "integration"
primaryQuery: "WhatsApp API 发送消息 示例"
keywords: ["WhatsApp API cURL示例", "Node.js发送WhatsApp模板", "WhatsApp API Python", "WhatsApp API PHP", "WhatsApp Business API发送消息", "WhatsApp会话消息API", "WhatsApp API multipart form-data", "WhatsApp API 出海"]
published: "2026-10-16"
author: "OmniChannels.ai Editorial Team"
reviewer: "BroadNet Messaging Solutions Team"
readingMinutes: 10
answer: "使用OmniChannels.ai API，所有WhatsApp消息都通过同一个端点POST https://api.broadnet.me/whatsapp/message/sendbulk发送，请求采用multipart表单数据，并带有Authorization: Bearer请求头。messageType字段决定负载类型：TEMPLATE用于已获批的模板，开启对话或在24小时窗口之外发送时必须使用；窗口内的回复则使用TEXT、IMAGE或INTERACTIVE。"
takeaways: ["所有WhatsApp消息都通过POST /whatsapp/message/sendbulk发送；messageType（TEMPLATE、TEXT、IMAGE、INTERACTIVE等）决定哪些字段为必填。", "请求是multipart/form-data，而不是JSON，因此Python需要使用files=，PHP需要使用数组作为请求体，以避免被URL编码。", "在24小时客户服务窗口之外，只能发送已获批的模板；窗口内则可以发送自由格式的文本、媒体和互动消息。", "该调用会把消息排入队列并立即返回batchId；每个收件人的状态会推送到您的Webhook，也可以通过bulk-insights端点获取。", "该API不接受幂等键，所以请在发送前先记录每次发送，超时后检查状态，而不是重发。"]
faq: 
  - q: "发送WhatsApp消息应该用JSON还是表单数据？"
    a: "表单数据。WhatsApp发送端点POST /whatsapp/message/sendbulk接受multipart/form-data，每个字段是一个表单部分。短信的单条发送则使用JSON，这是常见的混淆来源。在Python中请通过files=而不是data=发送字段，在PHP中请将数组作为请求体传入，这样cURL会构造multipart。"
  - q: "不使用模板可以发送WhatsApp消息吗？"
    a: "只有在客户服务窗口内才可以。客户给您发消息后，会开启24小时窗口，您可以用文本、媒体或互动消息回复。窗口之外，或要开启一段对话时，必须发送已获批的模板。API会执行Meta的规则，向已关闭的窗口发送自由格式消息会失败。"
  - q: "如何知道消息是否已送达？"
    a: "发送调用只确认消息已排入队列，并返回一个batchId。每个收件人的状态依次经过SUBMITTED、SENT、DELIVERED和READ，或以FAILED、REJECTED结束。它们会推送到您的Webhook，您也可以通过bulk-insights端点按批次读取。"
  - q: "可以在一个请求里发送给多个收件人吗？"
    a: "可以。destinationNo接受以逗号、分号或换行分隔的多个号码，destinationFiles接受TXT、CSV或XLSX文件。如果模板需要为每个收件人使用不同的变量，请上传每个收件人一行的个性化文件。将allowDuplicate设为false可以移除重复号码。"
  - q: "有防止重复发送的幂等键吗？"
    a: "没有。该API不接受幂等键。如果请求超时，您无法从客户端判断它是否已被接受。请在调用之前先在自己的数据库中记录每次发送，保存返回的batchId，并在任何重试之前检查状态或批次列表。"
  - q: "发送之前需要事先同意吗？"
    a: "需要。Meta的政策要求每位收件人都已同意接收来自您企业的消息，模板在使用前必须获批。API不会替您检查是否有用户同意。请在源头收集事先同意、保存证据，并屏蔽任何已退订的用户。"
sources: 
  - title: "WhatsApp Business消息政策"
    url: "https://business.whatsapp.com/policy"
  - title: "WhatsApp Business平台定价——Meta for Developers"
    url: "https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing"
  - title: "RFC 7578：表单值的返回：multipart/form-data"
    url: "https://www.rfc-editor.org/rfc/rfc7578"
---

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

Canonical: https://omnichannels.ai/zh/blog/send-whatsapp-message-api-examples/
Language: zh
Publisher: OmniChannels.ai by BroadNet Technologies

本指南面向将WhatsApp发送功能集成到应用中的开发者，提供cURL、Node.js、Python和PHP的可运行请求，说明模板消息与会话消息的区别，并涵盖状态跟踪和错误处理。每个端点和字段都来自我们的[WhatsApp API参考](/developers/whatsapp/)和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批次摘要 |

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

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

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

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

Meta按送达消息的类别收费，其关于服务消息和实用型（Utility）模板的规则已于2026年10月1日变更。我们的[窗口指南](/zh/blog/whatsapp-24-hour-customer-service-window/)介绍了运作机制，[价格页面](/zh/whatsapp-pricing/)和[计算器](/zh/tools/whatsapp-pricing-calculator/)则涵盖费用。

## 如何发送已获批的模板？

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

```bash
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
```

```js
// 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());
```

```python
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
<?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);
```

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

```json
{
  "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参考](/developers/whatsapp/)。

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

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

```bash
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数组。请在服务器端生成验证码，带有效期以哈希形式存储，并且绝不要写入日志。我们的[验证码指南](/zh/blog/otp-authentication-messages-guide/)介绍了一键、零点击和复制验证码的投递方式。

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

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

```bash
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`。

```bash
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`：

```bash
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`结束并附带原因。

```bash
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指南](/zh/blog/whatsapp-webhooks-guide/)构建您的接收端。批次还可以通过参考文档中的批次端点暂停、恢复和中止。

## 需要处理哪些错误？

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

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

我们的错误码指南说明了常见的Meta错误码。由于没有幂等键，请遵循[错误与限制](/developers/errors-and-limits/)中的重复发送防护步骤：先写入待处理记录，保存返回的`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文档](/developers/whatsapp/)或[快速入门](/developers/quickstart/)开始。
