如何用 API 发送 WhatsApp 消息(Python 与 curl)

发布于 2026年8月23日 · 更新于2026年10月9日

有时候只做号码校验还不够,你需要真正发送一条 WhatsApp 消息:一条引导文案、一条通知、一条客服回复,或一条交易提醒。WhatsApp 消息 API 通过普通的 HTTP 请求实现这些。你只需绑定一次 WhatsApp 账号,之后每次调用都带上得到的 sessionId。

本文用 curl 和 Python 走完整个流程:创建并绑定会话、发送文本和媒体、校验收件人、读取回复,以及如何让账号保持良好状态。请只在你本人拥有或已获授权的账号上使用,并遵守 WhatsApp 的服务条款。

工作原理

发送是基于会话的。一个会话对应一个已绑定的 WhatsApp 账号,流程是:

  1. 创建会话:发起登录,拿到会话 id。
  2. 绑定账号:在 WhatsApp 应用里扫描二维码,或输入 8 位配对码。
  3. 轮询会话:直到状态变为 CONNECTED。
  4. 发送消息:把 sessionId 传给各个发送接口。

连接成功后,会话 id 会变成由绑定设备派生的稳定 ID,重启后可继续使用,不必重新绑定。

第一步:获取 API Key

注册后在控制台复制一个有效 Key。账户赠送免费点数,可以完整跑通整个流程。

请求的组织方式

API Key 放在 key,会话放在 sessionId。GET 和 DELETE 接口的其余参数都放在查询字符串里;POST 接口则把 JSON 内容作为字符串放进 body 字段。每个响应都被包了两层,形如 {"code": 200, "message": "success", "data": {"code": 200, "data": {...}}}:外层由平台返回,内层来自消息服务,它始终返回 HTTP 200,失败时在自己的 code、title、detail 里说明原因。下面这个 Python 小封装把 body 字符串和两层响应都藏起来,在任何失败时抛出异常,后面每一步都会用到:

import json
import time
import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://api.opendata-api.com/api/project/whatsapp-messaging"
SUCCESS_CODES = {200, 201, 202, 204}


def call(method, operation, payload=None, **params):
    """Call one operation and return the innermost payload (dict, list or None)."""
    params["key"] = API_KEY
    kwargs = {"params": params, "timeout": 60}
    if payload is not None:
        kwargs["json"] = {"body": json.dumps(payload)}
    resp = requests.request(method, f"{BASE}/{operation}", **kwargs)
    if not resp.ok:
        raise RuntimeError(f"{operation} failed: HTTP {resp.status_code} {resp.text}")
    body = resp.json()
    # Peel both envelopes: {"code": 200, "data": {"code": 201, "data": {...}}}
    while isinstance(body, dict) and "code" in body:
        if body["code"] not in SUCCESS_CODES:
            raise RuntimeError(f"{operation} failed: {body}")
        body = body.get("data")
    return body

第二步:创建会话并绑定账号

create-session 支持两种登录方式,推荐用二维码登录:

curl -X POST "https://api.opendata-api.com/api/project/whatsapp-messaging/create-session?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body":"{\"mode\":\"qr\",\"deviceName\":\"My Bot\"}"}'

然后用会话 ID 轮询 get-session。登录等待期间,响应里会带有可扫描的 qrImage(base64 数据 URI)和 qrPageUrl,后者可以在浏览器里打开实时观察登录过程。在手机上打开 WhatsApp,进入「设置」,再进入「已关联设备」,选择「关联设备」并扫描二维码。

如果不方便扫码,可以改用配对码登录:发送 {"mode":"pairing","phoneNumber":"8613800138000","deviceName":"My Bot"},其中 phoneNumber 是完整的国际号码,只含数字,不带 + 和空格。之后状态响应会返回一个 8 位的 pairingCode。在手机上选择「关联设备」,再选「改为使用手机号码关联」,输入这个配对码即可。deviceName 是显示在「已关联设备」里的名称。

create-session 的响应把会话放在 data.data 里,内层 code 为 201。会话的标识是其中的 id 字段,之后每次调用都把它作为查询参数 sessionId 传入。

session = call("POST", "create-session", {"mode": "qr", "deviceName": "My Bot"})
# The identifier is the "id" field; "sessionId" is only the query-parameter name.
session_id = (session or {}).get("id")
if not session_id:
    raise RuntimeError(f"no session id in the response: {session}")

deadline = time.time() + 180
while time.time() < deadline:
    state = call("GET", "get-session", sessionId=session_id)
    print(state.get("status"), state.get("qrPageUrl"))
    if state.get("status") == "CONNECTED":
        break
    if state.get("status") in ("FAILED", "LOGGED_OUT"):
        raise RuntimeError(f"login failed: {state}")
    time.sleep(3)
else:
    raise TimeoutError("account was not linked in time")

可能出现的状态有 CREATED、WAITING_FOR_SCAN、CONNECTING、CONNECTED、DISCONNECTED、LOGGED_OUT 和 FAILED。

第三步:发送文本消息

会话状态变为 CONNECTED 后,调用 send-message,用 to 指定收件人,用 text 写入内容。这里有个几乎人人都会踩的坑:to 是完整的国际号码,只含数字、不带 +,并且必须是 JSON 数字而不是字符串。

curl -X POST "https://api.opendata-api.com/api/project/whatsapp-messaging/send-message?key=YOUR_API_KEY&sessionId=SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{"body":"{\"to\":8613800138000,\"text\":\"Hello from the API!\"}"}'
sent = call("POST", "send-message", {"to": 8613800138000, "text": "Hello from the API!"}, sessionId=session_id)
print(sent)   # id, timestamp and to

响应会返回消息 id、timestamp 和 to。如果想以引用回复的方式发送,加上 quoteMessageId,值取自收件箱或聊天记录里的消息 ID。

第四步:先校验收件人

给一个没有 WhatsApp 的号码发消息,既浪费调用,也会让账号看起来可疑。check-contact-exists 借助你已连接的会话,告诉你某个号码是否有可用的 WhatsApp 账号,响应里包含 exists、该联系人的 jid 以及你传入的 query。号码通过 phoneNumber 传入,同样只含数字。

info = call("GET", "check-contact-exists", sessionId=session_id, phoneNumber="8613800138000")
print(info.get("exists"), info.get("jid"))

如果是对整份名单做校验,不依赖会话的查询更合适,请阅读如何检测号码是否在用 WhatsApp、WhatsApp 号码查询指南以及批量验证 WhatsApp 号码。

第五步:发送媒体

send-media 用 to 指定收件人(同样是 JSON 数字),type 可选 image、video、audio、voice、document 或 sticker,文件内容用 base64 编码后放进 data。voice 表示发送按住说话的语音消息。发送 document 时必须提供 fileName;caption 用来添加说明文字,mimeType 可覆盖自动推断的类型,seconds 用来设置音频或视频的时长。

import base64

with open("receipt.pdf", "rb") as fh:
    data = base64.b64encode(fh.read()).decode("ascii")

call("POST", "send-media", {
    "to": 8613800138000,
    "type": "document",
    "data": data,
    "fileName": "receipt.pdf",
    "caption": "Your receipt",
}, sessionId=session_id)

第六步:读取回复

list-inbox 返回会话在线期间实时收到的消息,最新的在前。每条记录包含消息 ID、chatJid、发送者、类型、文本和时间戳。收件箱每个会话最多保留最近 500 条,重启后会被清空,所以请轮询它,并把需要的内容复制进自己的数据库。用 limit 限制每次返回的条数,再用 mark-read 传入 chatJid 和 messageIds 发送已读回执。

messages = call("GET", "list-inbox", sessionId=session_id, limit=50) or []
for msg in messages:
    print(msg["id"], msg["chatJid"], msg.get("text"))

错误处理与会话管理

  • 要看内层 code,不能只看 HTTP 状态码。 消息服务即使拒绝了请求也会返回 HTTP 200,结果放在内层的 code 里,并附带 title 和 detail,call() 会据此抛出异常。平台层的 401 表示 API Key 缺失或无效,403 表示点数不足或账户未启用。
  • 重试之前先检查会话。 如果发送开始失败,先调用 get-session。账号只是离线时,这个调用会帮你重新连上。
  • 不要盲目重试发送。 发送接口没有幂等键,超时后请先查看聊天记录,再决定是否重发。
  • 有意识地断开连接。 disconnect-session 只断开实时连接、保留已保存的登录。加上 logout=true 会彻底退出 WhatsApp,之后账号需要重新绑定。

请负责任地使用

未经许可的批量消息违反 WhatsApp 的条款,发送对方不想收到的消息的账号有被限制的风险。为了保持账号状态良好:

  • 只发给有预期的人:已授权的用户、客户和交易通知的接收者。
  • 不要群发冷名单。 先校验号码,只联系可触达且已同意的人。
  • 控制发送节奏,加上间隔,避免向很多人发送完全相同的文本。
  • 遵守 WhatsApp 的条款,以及适用于你业务场景的消息与隐私法规。

如果你的受众同时也在用 Telegram,同样的做法见用 Python 发送 Telegram 消息和 Telegram 登录教程。想决定每个联系人该走哪个渠道,请按线索核验工作流来做。

常见问题

怎么通过 API 发送 WhatsApp 消息? 先用 create-session 创建会话,通过扫描二维码或输入配对码绑定账号,等 get-session 返回 CONNECTED 之后,再带上 sessionId 调用 send-message,用 to 指定收件号码,用 text 写入内容。

为什么 send-message 拒绝我的号码? to 必须是完整的国际号码,只含数字、不带加号,并且要以 JSON 数字而不是字符串的形式发送。例如 8613800138000,而不是 "+86 138 0013 8000"。

每次都要扫二维码吗? 不用。首次绑定之后,会话 id 会变成由设备派生的稳定 ID,重启后依然有效。已保存但离线的账号,调用 get-session 或 connect-session 就能恢复。logout=true 会删除已保存的凭据,所以之后需要重新绑定。

能接收到来的 WhatsApp 消息吗? 可以。list-inbox 返回会话在线期间收到的消息,最新的在前,每个会话最多保留最近 500 条。重启后收件箱会被清空,需要的内容请自行保存。

可以用来做批量营销吗? 请把它当作处理对方预期之内的对话的工具。未经请求或重复内容的批量消息会让账号面临风险,所以只发给已同意的联系人,先校验号码,并且把发送量控制在合理范围内。

立即体验

在 WhatsApp 消息页面浏览完整接口集,并阅读 create-session、send-message 与 list-inbox 接口文档。查看定价了解每次请求的成本。