Files
lislgosms/docs/client-http-api-guide.md
T
2026-09-14 22:53:34 +08:00

49 KiB
Raw Blame History

客户 HTTP 短信接口接入手册

接口版本:v1 · 手册修订:2026-09-14

适用对象:需要从自己的业务系统发送短信、查询短信结果或接收用户回复的开发人员。

本文说明 HTTP 接口的接入步骤、鉴权、请求与响应以及回调处理。所有号码、消息编号和响应示例均为说明用途,不是真实客户数据。GET 空请求体使用第 3 节规定的兼容摘要;每次请求都须重新生成请求唯一标识和签名。

建议先完成上行查询与签名校对,再接入发送和回调。每个接口的参数说明后均附有对应示例;复制示例不会执行请求。

1. 开始接入前

1.1 这套接口能做什么

你的业务需求 使用的能力
向一个手机号发送一条短信 单条发送接口
知道短信是否提交、送达或失败 短信状态查询接口
短信有回执时,由平台主动通知你的系统 回执回调(Webhook
获取手机用户回复的短信 上行列表、上行详情,或上行回调

“上行短信”就是手机用户回复到短信接入号的内容。“回执”是短信提交后的送达或失败结果。两者不是同一种通知。

本版只提供四个公开业务接口,不提供公开的批量发送、余额查询、签名管理或模板管理接口。签名、模板和应用配置通过平台页面办理。

1.2 开户、开通和凭据

依次完成以下准备:

  1. 由平台创建企业账号和企业应用,并开通该应用的 HTTP 接口及需要的发送、查询、回调能力。
  2. 登录客户端,进入 接口对接,选择正确的企业应用。在“接口概览”确认接口地址、能力、QPS、时间容差和 HTTP IP 白名单。
  3. 在“访问凭据”创建凭据,保存 Access KeySecret。自助创建未开放时联系平台人员。
  4. 如果需要发送,先完成签名和模板审核、必要报备以及余额和可用路由准备。只开通 HTTP 不代表任意正文都可以发送。
  5. 如果需要回调,在“回调配置”分别保存回执、上行 HTTPS 地址,并保存各自的回调签名密钥。

Secret 只在创建或轮换时展示一次,后续末四位不能用于签名。凭据属于特定企业应用,不要将应用 A 的凭据用于查询应用 B 的短信。

名称 用途 是否放进请求头
Access Key 标识调用哪个企业应用的接口 放入 X-App-Key
Secret 在你的服务端计算请求签名 不直接发送
回调签名密钥 验证平台推送到你系统的通知 不直接发送;与请求 Secret 分开保存
平台登录密码 登录客户端网页 不用于 HTTP 接口签名

凭据应由你的后端服务保管,不放在网页前端代码、公开仓库或日志中。轮换请求凭据时,先建立新凭据、切换调用,再吊销旧凭据;可同时有效的数量以应用配置为准。回调密钥轮换需协调接收端,当前不要假设平台同时用新旧密钥签名。

1.3 地址与文档入口

用途 地址
客户接口基础地址 https://api.lisglo.com/api/openapi/v1
在线 Swagger https://api.lisglo.com/api/client-docs
OpenAPI JSON https://api.lisglo.com/api/client-docs-json
客户端文档入口 接口对接 → 选择企业应用 → 接口文档

上述域名当前连接预生产真实业务系统。未开户也能查看文档,但业务调用需要有效凭据;Swagger 的调试按钮会发真实请求,不是匿名试用或模拟发送。测试环境使用平台另行提供的地址和凭据,不能混用。

建议第一次先查询上行列表。 即使没有上行数据,也可以用空列表确认接入流程;不要用发送短信来测试网络是否通畅。

2. 四个接口一览

以下路径均在域名 https://api.lisglo.com 后拼接,已包含 /api,不要重复添加。

方法 完整路径 成功状态
POST /api/openapi/v1/sms/messages 202:请求受理
GET /api/openapi/v1/sms/messages/{messageId} 200:返回短信状态
GET /api/openapi/v1/sms/uplinks 200:返回上行列表
GET /api/openapi/v1/sms/uplinks/{uplinkId} 200:返回上行详情

所有接口都需要下面的鉴权头。请求和响应使用 UTF-8;POST 请求体使用 JSON。成功响应直接返回对象,没有额外的 data 包装层;回调通知则有自己的 data 字段。

3. 每次请求怎样签名

3.1 请求头

请求头 必填 填写方式
X-App-Key 该应用的 Access Key
X-Timestamp 当前 Unix 时间戳,单位为秒,例如 1789344000;不要传毫秒
X-Nonce 本次 HTTP 请求的唯一标识,推荐 UUID v4,例如 550e8400-e29b-41d4-a716-446655440000;兼容原 8128 字符规则
X-Signature 按下文计算的 HMAC-SHA256,小写十六进制字符串
Idempotency-Key 发送时必填 一次业务发送的稳定编号,8~128 字符,允许字母、数字、._:-
Content-Type POST 使用 application/json
User-Agent 可填写客户端名称和版本,便于排错

默认时间容差为正负 300 秒,具体以应用配置为准。服务器时钟应同步。每次请求包括网络重试都要生成新的 nonce;即使业务请求被限流,旧 nonce 也不能继续使用。

推荐用标准库生成 UUID v4,例如 Python str(uuid.uuid4())。当前服务端仍兼容字母、数字、下划线和短横线组成的 8~128 字符,不要求旧客户全部改成 UUID。不要使用订单号或仅有时间戳的值代替每次请求的新标识。

标识 代表的对象 同一次发送的网络重试
X-Nonce(请求唯一标识) 一次 HTTP 调用,防止请求重放 重新生成 UUID v4
Idempotency-Key(业务幂等键) 一次业务发送,防止重复创建短信 保持不变,并保持 body 原始字节相同
clientMessageId(客户短信编号) 客户系统中的短信业务记录 保持不变

3.2 计算请求签名

签名分为三个步骤:计算请求体摘要、拼接五行原文、计算 HMAC。HTTP 请求头中的时间戳和 nonce 必须与签名时使用的值完全一致。

第一步:计算请求体摘要。 POST 使用最终发送的 JSON 原始字节计算 SHA256;不要在签名后修改空格、字段顺序或中文转义。无请求体的 GET 使用第 3.3 节规定的兼容摘要。

第二步:按以下顺序拼接五行。 路径包含 /api,不含域名或查询参数;如路径参数经过 URL 编码,使用实际发送的编码后路径。

五行签名原文:

大写 HTTP 方法
请求路径
X-Timestamp 的原始字符串
X-Nonce 的原始字符串
请求体的 SHA256 十六进制摘要

相邻两行用一个 LF 换行符(0x0A)连接,最后一行后不加换行。不要使用 Windows CRLF,也不要加入引号或多余空格。URL 查询参数使用的 & 不是签名分隔符,query 不参与本版本签名。

第三步:计算 HMAC-SHA256。 使用 Secret 的 UTF-8 字节作为密钥,五行原文的 UTF-8 字节作为输入,输出 64 位十六进制字符串,放入 X-Signature。

签名计算公式:

SIGNING_STRING = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + BODY_HASH
SIGNATURE = HEX(HMAC_SHA256(UTF8(Secret), UTF8(SIGNING_STRING)))

公式中的 \n 表示一个 LF 字节,不是反斜杠和字母 n 两个字符。Secret 直接使用,不做 Base64 解码。示例输出纯十六进制签名;服务端也接受 sha256= 前缀,但无须添加。Idempotency-Key 是业务幂等头,不属于上述五行,也不能代替 nonce。

3.3 当前 GET 空请求体的兼容规则

当前版本的特殊行为:不带请求体的 GET,摘要需要按 UTF-8 字符串 {} 计算。GET 本身仍不发送请求体。

固定摘要为:

44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a

请勿使用空字节的 SHA256 代替这个摘要。本版本为兼容已有接入保留此规则;GET 请求仍不携带 body。

3.4 Python 签名函数

下面只计算请求头,不访问网络。使用 Python 标准库即可。

import hashlib
import hmac
import uuid
import time


def make_headers(method, path, raw_body, access_key, secret,
                 timestamp=None, nonce=None):
    # path 必须是实际发送的路径,不包含域名和 query。
    if not path.startswith("/api/openapi/v1/") or "?" in path or "#" in path:
        raise ValueError("请传入不含 query 的完整接口路径")
    timestamp = str(int(time.time()) if timestamp is None else timestamp)
    nonce = str(uuid.uuid4()) if nonce is None else nonce
    # None 表示 GET 无请求体;兼容当前服务端的 {} 摘要规则。
    bytes_to_hash = b"{}" if raw_body is None else raw_body
    body_hash = hashlib.sha256(bytes_to_hash).hexdigest()
    source = "\n".join([method.upper(), path, timestamp, nonce, body_hash])
    signature = hmac.new(
        secret.encode("utf-8"), source.encode("utf-8"), hashlib.sha256
    ).hexdigest()
    return {
        "X-App-Key": access_key,
        "X-Timestamp": timestamp,
        "X-Nonce": nonce,
        "X-Signature": signature,
    }

不要把函数生成的请求头或真实 Secret 完整打印到日志。

3.5 固定签名校对样例(不用于线上请求)

使用虚构 Secret doc-example-secret、时间戳 1789344000、nonce 550e8400-e29b-41d4-a716-446655440000GET 路径 /api/openapi/v1/sms/uplinks,不发送 body。按当前兼容规则,实际签名字符串为:

GET
/api/openapi/v1/sms/uplinks
1789344000
550e8400-e29b-41d4-a716-446655440000
44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a

上面共有 4 个 LF 分隔字节,结尾没有 LF。期望签名为:

f551ad48ea2a16762b0144f0f0d6e9110c1732adc003fcb94658e5333116eb65

使用第 3.4 节函数并显式传入上述 timestamp 和 nonce,应得到完全相同的 X-Signature。这些固定值只用于离线校对;真实请求使用当前时间、新 UUID 和自己的凭据。

3.6 公共准备与占位符

本节每个接口均提供 cURL 请求、成功和常见失败 HTTP 响应;回调提供平台 POST 和客户 ACK。为便于阅读,HTTP 报文省略 Content-Length、Date、连接等由服务器或 HTTP 客户端生成的头;不要把签名原文的 LF 规则套到 HTTP 报文行结束符上。

cURL 使用 Bash 语法(PowerShell 应使用 curl.exe 并调整续行/变量语法)。$ACCESS_KEY$TIMESTAMP$NONCE$SIGNATURE 分别对应第 3.4 节函数返回的四个头;$CURSOR 必须取上一页真实 nextCursor。这些不是固定测试凭据。下面的可视 cURL 不能在未生成动态鉴权值时直接调用;固定签名只用于第 3.5 节离线校对。

下面的公共辅助函数引用第 3.4 节 make_headers,直接生成带动态签名的 cURL 参数和 stdin 字节,免去手工设置上述变量。将它与签名函数放在同一 Python 文件,从安全环境变量读取凭据。函数本身不访问网络;不要打印返回的完整鉴权参数。

import os
from urllib.parse import urlencode


def prepare_curl(method, path, raw_body=None, query=None, idempotency_key=None):
    method = method.upper()
    if method == "POST" and (raw_body is None or not idempotency_key):
        raise ValueError("发送需提供最终原始字节和稳定幂等键")
    headers = make_headers(method, path, raw_body,
                           os.environ["CMPP_HTTP_ACCESS_KEY"],
                           os.environ["CMPP_HTTP_SECRET"])
    url = "https://api.lisglo.com" + path
    if query:
        url += "?" + urlencode(query)
    args = ["curl", "--include", "--max-time", "15", "--request", method, url]
    for key, value in headers.items():
        args.extend(["--header", f"{key}: {value}"])
    if raw_body is not None:
        args.extend(["--header", "Content-Type: application/json",
                     "--data-binary", "@-"])
    if idempotency_key:
        args.extend(["--header", f"Idempotency-Key: {idempotency_key}"])
    return args, raw_body

例如 args, body = prepare_curl("GET", "/api/openapi/v1/sms/uplinks", query={"limit": 10}) 只准备查询。由客户在确认环境后使用 subprocess.run(args, input=body, check=True) 实际调用;HTTP 状态仍须单独判断,cURL 进程退出 0 不代表业务成功。POST 将完整 JSON 字节通过 stdin 传入,避免重新序列化;本稿没有自动执行发送的入口。

4. 第一个调用:查询最近的上行短信

将第 3.4 节函数和下面代码放在同一个 Python 文件。通过本机安全配置提供 CMPP_HTTP_ACCESS_KEYCMPP_HTTP_SECRET 环境变量,再运行脚本。此代码只执行 GET,不发送短信。

import json
import os
import urllib.error
import urllib.parse
import urllib.request

origin = "https://api.lisglo.com"
path = "/api/openapi/v1/sms/uplinks"
query = urllib.parse.urlencode({"limit": 10})
headers = make_headers(
    "GET", path, None,
    os.environ["CMPP_HTTP_ACCESS_KEY"],
    os.environ["CMPP_HTTP_SECRET"],
)
headers["Accept"] = "application/json"
request = urllib.request.Request(
    origin + path + "?" + query, headers=headers, method="GET"
)
try:
    with urllib.request.urlopen(request, timeout=15) as response:
        result = json.loads(response.read().decode("utf-8"))
        # 只输出计数,不在示例中打印客户手机号和短信正文。
        print("HTTP", response.status)
        print("本页条数:", len(result["items"]))
        print("是否有下一页:", result["nextCursor"] is not None)
except urllib.error.HTTPError as error:
    try:
        problem = json.loads(error.read().decode("utf-8"))
    except (ValueError, UnicodeError):
        problem = {}
    print("HTTP", error.code, "业务码:", problem.get("code", "非标准错误响应"))
except urllib.error.URLError:
    print("连接失败,请检查网络、域名及 HTTPS 连通性")

没有匹配到上行数据时,正常返回:

{"items": [], "nextCursor": null}

空列表不是失败。401 优先检查签名,403 检查应用能力与白名单,429 降低请求频率。

5. 发送单条短信

5.1 请求

POST /api/openapi/v1/sms/messages

本节后附完整 cURL 请求和 HTTP 响应示例。

除鉴权头外,需要 Content-Type: application/jsonIdempotency-Key

JSON 字段 类型 必填 说明
mobile string 单个中国大陆手机号,例如 13800138000;不能传数组或逗号分隔号码
content string 完整短信正文,包含签名和已填入的变量值;不能为空
clientMessageId string 否,建议提供 你的业务系统消息编号,应用内唯一;按不超过 128 字符使用
{
  "mobile": "13800138000",
  "content": "【示例签名】您的验证码是123456,5分钟内有效。",
  "clientMessageId": "order-20260914-0001"
}

正文由平台识别签名、模板和变量,不传 tenantIdapplicationIdsignatureIdtemplateId 或自选通道。上例签名和模板仅作说明,发送前必须使用你已审核且符合发送条件的内容。

下面代码只准备请求,不执行发送;接在第 3.4 节函数之后使用:

import json

payload = {
    "mobile": "13800138000",
    "content": "【示例签名】您的验证码是123456,5分钟内有效。",
    "clientMessageId": "order-20260914-0001",
}
raw_body = json.dumps(
    payload, ensure_ascii=False, separators=(",", ":")
).encode("utf-8")
path = "/api/openapi/v1/sms/messages"
# access_key、secret 从你的安全配置读取。
headers = make_headers("POST", path, raw_body, access_key, secret)
headers["Content-Type"] = "application/json"
headers["Idempotency-Key"] = "sms-order-20260914-0001"
# 由你的发送代码将同一个 raw_body 原样交给 HTTP 客户端。
# 不要让 HTTP 客户端再把 payload 重新序列化成另一组字节。

5.2 受理响应

成功受理返回 HTTP 202,示例:

{
  "code": "ACCEPTED",
  "requestId": "req_11111111-1111-4111-8111-111111111111",
  "messageId": "MSG-22222222-2222-4222-8222-222222222222",
  "clientMessageId": "order-20260914-0001",
  "status": "queued",
  "acceptedAt": "2026-09-14T02:00:00.000Z"
}
字段 含义
code ACCEPTED 表示接口受理成功
requestId 本次业务请求编号,排查问题时提供给平台
messageId 平台短信编号,用于后续查询;请持久保存
clientMessageId 你传入的编号,未提供时为 null
status 当前短信或任务状态,不保证总是 queued,也可能需要审核
acceptedAt 受理响应时间,ISO 8601;示例中的 Z 表示 UTC

202 ≠ 已提交运营商 ≠ 手机已收到。 请用状态查询或回执判断结果;HTTP 200/202 也不能直接解释为计费成功或退款完成。若 202 缺少 messageId,保留 requestId 和 clientMessageId 联系平台核查,不因缺失就创建新发送。

5.3 重复请求与超时处理

情况 处理方式
网络超时,不确定平台是否受理 先按 clientMessageId 查询;必要时用原 Idempotency-Key 和完全相同的 body 重试请求,重新生成 timestamp/nonce/签名
相同幂等键、相同 body,原请求已完成 返回原受理响应,原编号和 acceptedAt 保持不变
相同幂等键、相同 body,原请求业务失败已保存 重放原失败,不会因稍后改好配置就自动重新发送
相同幂等键、不同 body 409 IDEMPOTENCY_CONFLICT
原请求仍处理中 409 REQUEST_PROCESSING;稍后查询,长期不结束联系平台
换了幂等键,但 clientMessageId 已关联短信 409 CLIENT_MESSAGE_ID_CONFLICT

同一业务短信的幂等键要持久保存。JSON 字段顺序、空格变化也可能被视为不同 body。不要为了绕过 409 换键反复发送。修正确定失败的业务后是否发起新发送,由你的业务流程明确决定。

5.4 单条发送

将第 5.1 节 JSON 保存为 UTF-8 无 BOM 的 sms.json,签名时读取该文件的原始字节,随后原样发送。此命令会真实提交短信,示例不自动执行。

curl --include --max-time 15 --request POST \
  "https://api.lisglo.com/api/openapi/v1/sms/messages" \
  --header "X-App-Key: $ACCESS_KEY" \
  --header "X-Timestamp: $TIMESTAMP" \
  --header "X-Nonce: $NONCE" \
  --header "X-Signature: $SIGNATURE" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: sms-order-20260914-0001" \
  --data-binary "@sms.json"

成功响应示例:

HTTP/1.1 202 Accepted
Content-Type: application/json; charset=utf-8

{
  "code": "ACCEPTED",
  "requestId": "req_11111111-1111-4111-8111-111111111111",
  "messageId": "MSG-22222222-2222-4222-8222-222222222222",
  "clientMessageId": "order-20260914-0001",
  "status": "queued",
  "acceptedAt": "2026-09-14T02:00:00.000Z"
}

失败响应示例:

HTTP/1.1 409 Conflict
Content-Type: application/problem+json; charset=utf-8

{
  "type": "https://cmpp-platform.local/problems/idempotency_conflict",
  "title": "CONFLICT",
  "status": 409,
  "code": "IDEMPOTENCY_CONFLICT",
  "detail": "同一Idempotency-Key对应的请求内容不一致"
}

5.4.1 完整生成脚本:不再手工填写请求头变量

上面的 $NONCE$TIMESTAMP 等是 Bash 变量引用,不是可以原样发送的参数值。未设置变量时,单独复制上面的结构示例不能正常鉴权。

下面提供完整脚本:实际生成 UUID v4、当前时间戳、请求体摘要和 HMAC-SHA256 签名,再输出没有请求头变量占位符的 Bash cURL 命令。保存为 UTF-8 编码的 generate_sms_curl.py,运行 python3 generate_sms_curl.py

脚本只生成并打印命令,不访问接口、不发送短信。UUID 和签名是真实生成、真实计算的;Access Key 和 Secret 明确为未开户的演示凭据,不能据此鉴权成功。

import hashlib
import hmac
import json
import shlex
import time
import uuid

# 演示凭据,不是真实客户账号。
ACCESS_KEY = "DEMO_ACCESS_KEY_NOT_REGISTERED"
SECRET = "DEMO_SECRET_NOT_A_REAL_CREDENTIAL"

origin = "https://api.lisglo.com"
path = "/api/openapi/v1/sms/messages"

# 同一次业务发送重试时,两个业务编号和请求体保持不变。
idempotency_key = "sms-doc-example-20260914-0001"
payload = {
    "mobile": "13800138000",
    "content": "【示例签名】您的验证码是123456,5分钟内有效。",
    "clientMessageId": "doc-example-20260914-0001",
}

# 只序列化一次:计算摘要与最终发送使用同一份内容。
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
raw_body = body.encode("utf-8")
timestamp = str(int(time.time()))
nonce = str(uuid.uuid4())
body_hash = hashlib.sha256(raw_body).hexdigest()
signing_string = "\n".join([
    "POST", path, timestamp, nonce, body_hash,
])
signature = hmac.new(
    SECRET.encode("utf-8"),
    signing_string.encode("utf-8"),
    hashlib.sha256,
).hexdigest()

headers = {
    "Content-Type": "application/json",
    "X-App-Key": ACCESS_KEY,
    "X-Timestamp": timestamp,
    "X-Nonce": nonce,
    "X-Signature": signature,
    "Idempotency-Key": idempotency_key,
}
lines = [
    "curl --include --max-time 15 --request POST "
    + shlex.quote(origin + path)
]
for name, value in headers.items():
    lines.append("--header " + shlex.quote(f"{name}: {value}"))
lines.append("--data-binary " + shlex.quote(body))

print("本次实际生成的 UUID", nonce)
print("本次时间戳:", timestamp)
print("请求体 SHA256", body_hash)
print("计算得到的签名:", signature)
print("\n完整 cURL 命令(仅生成,未执行):\n")
print(" \\\n  ".join(lines))

该脚本每次运行都会产生新的 nonce 和时间戳,因而签名也会变化。输出命令按 Bash 和 UTF-8 编写,不应直接当作 PowerShell 语法执行。脚本用于演示,会打印鉴权头;正式接入时凭据从安全配置读取,不把完整鉴权信息写入共享日志。

5.4.2 实际计算的完整 cURL 示例

以下为2026-09-14实际生成的一组结果,不是 $NONCE$SIGNATURE 占位符,也不是成功发送记录:

项目 本次实际使用或计算的值
演示 Access Key DEMO_ACCESS_KEY_NOT_REGISTERED(未开户)
演示 Secret DEMO_SECRET_NOT_A_REAL_CREDENTIAL(无真实账号权限)
Unix 秒级时间戳 1789355443
实际生成的 UUID v4 7921b5d1-3b99-48d4-a068-ea7cf0c998db
原始请求体 SHA256 c6cd931342e983778342e2175c0c60eafc87f28c892378761d0e6f7065728a29
实际计算的 HMAC-SHA256 90cd7c99308406868fcdad059a9e61aef0a5d1275756885c4e277aa20e787a4b
curl --include --max-time 15 --request POST \
  'https://api.lisglo.com/api/openapi/v1/sms/messages' \
  --header 'Content-Type: application/json' \
  --header 'X-App-Key: DEMO_ACCESS_KEY_NOT_REGISTERED' \
  --header 'X-Timestamp: 1789355443' \
  --header 'X-Nonce: 7921b5d1-3b99-48d4-a068-ea7cf0c998db' \
  --header 'X-Signature: 90cd7c99308406868fcdad059a9e61aef0a5d1275756885c4e277aa20e787a4b' \
  --header 'Idempotency-Key: sms-doc-example-20260914-0001' \
  --data-binary '{"mobile":"13800138000","content":"【示例签名】您的验证码是1234565分钟内有效。","clientMessageId":"doc-example-20260914-0001"}'

真实的是 UUID 生成过程、摘要和签名计算结果,不是客户凭据或业务调用成功。 固定示例可离线复算,但演示凭据无效,时间戳也会过期。本次未执行这条命令。使用真实凭据时,必须重新生成时间戳、nonce 和签名,不能只替换 Access Key;执行有效的发送命令可能真正创建短信任务并产生费用。

6. 查询短信状态

完整 cURL 请求和 HTTP 响应见本节后附示例。

GET /api/openapi/v1/sms/messages/{messageId},无请求体。

路径末尾可以填写平台返回的 messageId,也支持原来提交的 clientMessageId。请对路径参数做 URL 编码;只能查询当前凭据所属应用的数据。

示例响应:

{
  "messageId": "MSG-22222222-2222-4222-8222-222222222222",
  "clientMessageId": "order-20260914-0001",
  "phoneNumber": "13800138000",
  "status": "delivered",
  "submitStatus": "accepted",
  "receiptStatus": "delivered",
  "errorCode": null,
  "errorMessage": null,
  "queuedAt": "2026-09-14T02:00:00.000Z",
  "submittedAt": "2026-09-14T02:00:01.000Z",
  "deliveredAt": "2026-09-14T02:00:03.000Z",
  "updatedAt": "2026-09-14T02:00:03.100Z"
}
字段 类型 说明
messageId string 平台短信编号
clientMessageId string/null 客户消息编号
phoneNumber string 接收手机号;注意响应字段不是 mobile
status string 平台业务状态
submitStatus string/null 提交状态,如 queued、accepted、rejected、timeout
receiptStatus string/null 回执状态,如 delivered、undelivered、unknownnull 表示尚无结果
errorCode / errorMessage string/null 失败码与说明,未发生或暂无时可为空
queuedAt / updatedAt string 入队记录时间、最近更新时间
submittedAt / deliveredAt string/null 提交时间、结果时间,尚无数据时可为空

常见 status 的业务含义:

状态 你应如何理解
pending_review 等待审核,不代表已送出
queued / submit_queued 等待平台处理或向通道提交
submitted 已提交,等待明确回执
delivered 已获得送达结果
submit_failed / failed / rejected 提交失败、发送失败或业务拒绝,结合错误字段查看原因
unknown / timeout 结果未知或等待超时,不能当成送达

上述是常见值,不是服务端严格冻结的全部枚举。遇到其他值保留原值并展示“待确认”,不要默认成功。deliveredAt 在失败回执中也可能是结果发生时间,不能只看这个字段非空就认为送达。

不存在或不属于本应用时返回 404 MESSAGE_NOT_FOUND。查询刚超时的发送得到 404 也不能单独证明业务永远不会落库。

6.1 短信状态

curl --include --max-time 15 --request GET \
  "https://api.lisglo.com/api/openapi/v1/sms/messages/MSG-22222222-2222-4222-8222-222222222222" \
  --header "X-App-Key: $ACCESS_KEY" \
  --header "X-Timestamp: $TIMESTAMP" \
  --header "X-Nonce: $NONCE" \
  --header "X-Signature: $SIGNATURE"

成功响应示例:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "messageId": "MSG-22222222-2222-4222-8222-222222222222",
  "clientMessageId": "order-20260914-0001",
  "phoneNumber": "13800138000",
  "status": "delivered",
  "submitStatus": "accepted",
  "receiptStatus": "delivered",
  "errorCode": null,
  "errorMessage": null,
  "queuedAt": "2026-09-14T02:00:00.000Z",
  "submittedAt": "2026-09-14T02:00:01.000Z",
  "deliveredAt": "2026-09-14T02:00:03.000Z",
  "updatedAt": "2026-09-14T02:00:03.100Z"
}

失败响应示例:

HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8

{
  "type": "https://cmpp-platform.local/problems/message_not_found",
  "title": "NOT_FOUND",
  "status": 404,
  "code": "MESSAGE_NOT_FOUND",
  "detail": "短信记录不存在"
}

7. 查询手机用户回复(上行)

7.0 先分清四种时间边界

项目 当前规则 举例
默认查询范围 endTime 默认现在;startTime 默认 endTime 前 24 小时 什么时间都不传,就是最近 24 小时
单次最大查询跨度 默认 31 天,以应用配置为准;限制起止时间之差 可查询三个月前某一天,并不是只能查询最近 31 天
历史数据保留期 有默认 90 天的配置,但尚未确认存在按该配置执行的保留/清理闭环 不能承诺一定有 90 天数据,也不能断言更早数据必定被删除
游标有效期 当前游标没有单独过期计时;不保证数据长期不变 不将旧 cursor 当作永久同步凭据,重新固定时间窗口并去重

查询代码没有额外强制“只查最近 N 天”,但历史查询能否返回数据取决于实际留存、当前应用归属和筛选条件。startTime 必须不晚于 endTime,当前时间边界两端均包含。跨窗口同步可按上行 id 去重,避免公共边界重复计入。

时间接受 YYYY-MM-DD(按UTC零点)或带 Z / ±HH:mm 时区的ISO 8601时间;秒可省略,小数秒按毫秒精度解析,更高精度输入保留兼容。日期必须在日历上真实存在,不接受2月30日、24:00、无时区日期时间或本地化日期格式;空字符串也不是省略参数。游标中的时间执行同样校验。

7.1 列表与筛选

第一页、空数据及下一页的完整报文见第 7.1、7.2 节。

GET /api/openapi/v1/sms/uplinks,无请求体。

Query 参数 类型 默认值/规则
startTime ISO 8601 时间字符串 默认 endTime 向前 24 小时
endTime ISO 8601 时间字符串 默认当前时间
mobile string 可选,精确匹配回复者手机号
accessNumber string 可选,精确匹配回复到的接入号,对应响应 destId
keyword string 可选,按回复正文包含关键词筛选
limit 正整数 默认 50;不超过应用最大分页配置,系统默认上限 100
cursor string 第一页不传,下一页使用上一响应 nextCursor

单次时间跨度默认上限 31 天,以应用配置为准。建议总是带时区,例如 2026-09-14T00:00:00+08:002026-09-13T16:00:00ZURL 中的 + 应通过标准 query 编码转换为 %2B。空筛选项直接省略,不传空字符串。limit 按上述正整数约定使用,不依赖当前不完善的非法参数处理。

示例请求 URL

https://api.lisglo.com/api/openapi/v1/sms/uplinks?startTime=2026-09-13T16%3A00%3A00Z&endTime=2026-09-14T16%3A00%3A00Z&limit=25

签名的路径仍为 /api/openapi/v1/sms/uplinks,不包含 ? 后的内容。

示例响应:

{
  "items": [
    {
      "id": "uplink-example-001",
      "messageId": null,
      "phoneNumber": "13800138000",
      "destId": "106900000000",
      "content": "收到,谢谢",
      "receivedAt": "2026-09-14T02:05:00.000Z"
    }
  ],
  "nextCursor": null
}

id 是上行记录编号,用它查询详情;messageId 可能为空,不用它代替上行 id。destId 是接收用户回复的短信接入号。内部匹配状态与诊断不对外返回。只返回归属本应用的已匹配/已认领数据,不返回未匹配或存在归属歧义的数据。

7.1.1 上行列表第一页

curl --include --max-time 15 --request GET \
  "https://api.lisglo.com/api/openapi/v1/sms/uplinks" --get \
  --data-urlencode "startTime=2026-09-13T16:00:00Z" \
  --data-urlencode "endTime=2026-09-14T16:00:00Z" \
  --data-urlencode "limit=25" \
  --header "X-App-Key: $ACCESS_KEY" \
  --header "X-Timestamp: $TIMESTAMP" \
  --header "X-Nonce: $NONCE" \
  --header "X-Signature: $SIGNATURE"

成功响应示例:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "items": [
    {
      "id": "uplink-example-001",
      "messageId": null,
      "phoneNumber": "13800138000",
      "destId": "106900000000",
      "content": "收到,谢谢",
      "receivedAt": "2026-09-14T02:05:00.000Z"
    }
  ],
  "nextCursor": null
}

失败响应示例:

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json; charset=utf-8

{
  "type": "https://cmpp-platform.local/problems/time_range_too_large",
  "title": "BAD_REQUEST",
  "status": 400,
  "code": "TIME_RANGE_TOO_LARGE",
  "detail": "单次查询不能超过31天"
}

空列表响应:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "items": [],
  "nextCursor": null
}

7.2 翻页方法

  1. 第一页固定 startTime、endTime 和筛选条件,不传 cursor。
  2. 若 nextCursor 非 null,下一次保持时间、筛选和 limit 不变,只加入 cursor;仍需新的 timestamp/nonce/签名。
  3. nextCursor 为 null 时结束。没有总条数、页码或 totalPages。

记录按接收时间、记录 id 倒序返回。cursor 是不透明令牌,不自行解码、构造或当作长期同步水位。上行可能延迟到达或稍后才被认领;持续同步宜重叠查询时间窗口并按 id 去重,不把一次分页当作永不变化的快照。

7.2.1 上行下一页

第 7.1.1 节只有一条记录的示例已结束,所以 nextCursor 为 null,不应继续请求。以下表示另一个有两条记录的分页场景,第一页和下一页均使用 limit=1。第一页省略 cursor,其他时间条件与第 7.1.1 节相同。将下面 nextCursor 完整保存到 $CURSOR;这是由虚构记录生成的格式示例,线上必须用真实上一响应的值。

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "items": [
    {
      "id": "uplink-example-001",
      "messageId": null,
      "phoneNumber": "13800138000",
      "destId": "106900000000",
      "content": "收到,谢谢",
      "receivedAt": "2026-09-14T02:05:00.000Z"
    }
  ],
  "nextCursor": "WyIyMDI2LTA5LTE0VDAyOjA1OjAwLjAwMFoiLCJ1cGxpbmstZXhhbXBsZS0wMDEiXQ"
}

保持第一页时间和筛选不变,生成新的请求唯一标识、时间戳和签名:

curl --include --max-time 15 --get \
  "https://api.lisglo.com/api/openapi/v1/sms/uplinks" \
  --data-urlencode "startTime=2026-09-13T16:00:00Z" \
  --data-urlencode "endTime=2026-09-14T16:00:00Z" \
  --data-urlencode "limit=1" \
  --data-urlencode "cursor=$CURSOR" \
  --header "X-App-Key: $ACCESS_KEY" \
  --header "X-Timestamp: $TIMESTAMP" \
  --header "X-Nonce: $NONCE" \
  --header "X-Signature: $SIGNATURE"

下一页返回较早的第二条记录,nextCursor 为 null,表示没有更多页:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "items": [
    {
      "id": "uplink-example-000",
      "messageId": null,
      "phoneNumber": "13800138000",
      "destId": "106900000000",
      "content": "好的",
      "receivedAt": "2026-09-14T02:04:00.000Z"
    }
  ],
  "nextCursor": null
}

非法游标示例:

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json; charset=utf-8

{
  "type": "https://cmpp-platform.local/problems/cursor_invalid",
  "title": "BAD_REQUEST",
  "status": 400,
  "code": "CURSOR_INVALID",
  "detail": "cursor格式非法"
}

7.3 上行详情

完整 cURL 请求和 HTTP 响应见第 7.3.1 节。

GET /api/openapi/v1/sms/uplinks/{uplinkId},无请求体。

把列表的 items[].id 或回调的 data.uplinkMessageId 放入路径,按第 3 节签名。返回单个上行对象,核心字段与列表中的 item 相同,不再包裹 items。

详情额外返回当前客户自己的 tenantIdapplicationId。不返回通道、供应商编号、内部事件/匹配诊断、数据库关联行号等字段。本次按数据边界收紧旧输出,旧客户需移除对这些内部字段的依赖。不存在或不属于当前企业应用时返回 404 UPLINK_NOT_FOUND

7.3.1 上行详情

curl --include --max-time 15 --request GET \
  "https://api.lisglo.com/api/openapi/v1/sms/uplinks/uplink-example-001" \
  --header "X-App-Key: $ACCESS_KEY" \
  --header "X-Timestamp: $TIMESTAMP" \
  --header "X-Nonce: $NONCE" \
  --header "X-Signature: $SIGNATURE"

成功响应示例:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "id": "uplink-example-001",
  "messageId": null,
  "phoneNumber": "13800138000",
  "destId": "106900000000",
  "content": "收到,谢谢",
  "receivedAt": "2026-09-14T02:05:00.000Z",
  "tenantId": "tenant-example",
  "applicationId": "application-example"
}

失败响应示例:

HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8

{
  "type": "https://cmpp-platform.local/problems/uplink_not_found",
  "title": "NOT_FOUND",
  "status": 404,
  "code": "UPLINK_NOT_FOUND",
  "detail": "上行记录不存在"
}

8. 平台怎样主动通知你的系统

8.1 配置与接收

在客户端“回调配置”分别设置:

  • 回执回调 URL:接收 receipt,用于获知短信结果。
  • 上行回调 URL:接收 uplink,用于接收用户回复。

平台向对应 URL 发起 HTTP POST。需开通该类回调能力、保存非空有效地址。使用平台可访问的公网 HTTPS 地址,不使用本机、内网或依赖重定向的地址。每个回调端点使用自己的签名密钥。

不要假设每次发送请求都会有回调。同步参数错误、未受理的请求以及未生成该类事件的分支,应处理接口响应或查询结果。长短信的 HTTP 结果通知按业务消息处理,不按运营商内部计费分片逐片回调。

8.2 请求头和事件格式

两类通知的完整 HTTP 请求及接收端响应见第 8.5 节。

回调头 说明
Content-Type application/json
X-Event-Id 稳定事件编号,用于去重
X-Event-Type receiptuplink
X-Timestamp 本次投递的 Unix 秒级时间戳
X-Signature sha256= 加 HMAC-SHA256 十六进制值

回执示例:

{
  "eventId": "evt_receipt_example001",
  "eventType": "receipt",
  "occurredAt": "2026-09-14T02:00:03.100Z",
  "data": {
    "messageId": "MSG-22222222-2222-4222-8222-222222222222",
    "gatewayMessageId": "example-gateway-id",
    "phoneNumber": "13800138000",
    "receiptStatus": "delivered",
    "rawStatus": "DELIVRD",
    "errorCode": null,
    "deliveredAt": "2026-09-14T02:00:03.000Z"
  }
}

receiptStatus 表示结果,rawStatus 是原始状态,errorCode 可空,部分失败事件还含 errorMessage。网关编号只作为辅助信息,使用 messageId 与自己的发送记录关联。当前回执不保证带 clientMessageId,请保存发送响应中的两种编号映射。未提供的可选字段可能缺省,也可能为 null。

上行示例:

{
  "eventId": "evt_uplink_example001",
  "eventType": "uplink",
  "occurredAt": "2026-09-14T02:05:00.100Z",
  "data": {
    "applicationId": "application-example",
    "phoneNumber": "13800138000",
    "destId": "106900000000",
    "content": "收到,谢谢",
    "receivedAt": "2026-09-14T02:05:00.000Z",
    "uplinkMessageId": "uplink-example-001"
  }
}

上行 data 还可能包含 messageId,人工认领的通知可含 manualClaim: true;没有 messageId 时不能强行关联一条发送记录。occurredAt 是平台创建事件的时间,与数据里的 deliveredAt/receivedAt 含义不同;重试时事件编号和事件体保持不变,投递时间戳与签名会重新生成。

8.3 如何验签

回调的签名原文是 时间戳 + 一个 LF 换行 + 原始请求体字节,使用该回调端点的签名密钥执行 HMAC-SHA256。它不是第 3 节的五行请求签名。

先保留 HTTP 原始 body 再验签,不要解析 JSON 后重新序列化。下面是纯计算示例;header 名大小写不敏感:

import hashlib
import hmac
import json
import time


def verify_callback(headers, raw_body, webhook_secret, now=None,
                    allowed_skew_seconds=300):
    # 300 秒是本示例的接收端策略,可按双方约定调整。
    hs = {str(k).lower(): str(v) for k, v in headers.items()}
    timestamp = hs.get("x-timestamp", "")
    if not timestamp.isdigit():
        return False
    now = time.time() if now is None else now
    if abs(now - int(timestamp)) > allowed_skew_seconds:
        return False
    supplied = hs.get("x-signature", "")
    if not supplied.startswith("sha256="):
        return False
    source = timestamp.encode("utf-8") + b"\n" + raw_body
    expected = hmac.new(
        webhook_secret.encode("utf-8"), source, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected, supplied[7:]):
        return False
    try:
        event = json.loads(raw_body)
    except (ValueError, UnicodeError):
        return False
    if not isinstance(event, dict):
        return False
    return (
        isinstance(event.get("eventId"), str)
        and bool(event["eventId"])
        and event.get("eventId") == hs.get("x-event-id")
        and event.get("eventType") == hs.get("x-event-type")
        and event.get("eventType") in {"receipt", "uplink"}
        and isinstance(event.get("data"), dict)
    )

验签后,仍需在数据库以 eventId 做唯一约束或等效的原子去重,不能只用进程内集合。对重复且已成功保存的事件正常确认;不要因重复而再次更新余额、发券或触发另一条短信。不同事件的到达先后也不能代替业务状态判断。

8.4 怎样确认接收、失败后会怎样

平台把 任意 HTTP 2xx 视为接收成功,不要求固定响应 JSON;可以返回 200 OK。建议先验签并将事件可靠保存,再尽快返回 2xx,后续业务异步处理。返回了 2xx 即使正文写“失败”,平台也按成功处理。

按设计:网络错误、408、429、5xx 可重试;其他 4xx 终结,重定向不跟随。默认总尝试上限 7 次(含首次),预期各次失败后的间隔为 1 分钟、5 分钟、15 分钟、1 小时、6 小时、24 小时,并受应用策略限制。这些是相邻尝试的等待间隔,不是统一从首次起算。默认配置超时为 10 秒,不应依赖它作为严格端到端时限。

平台会保存回调待办,并按重试规则处理暂时失败。网络中断可能造成同一事件被重复接收,请始终按 eventId 去重。对于长期未收到的历史事件,请先查询投递记录并联系平台核对,不要将其视为短信发送失败。

8.5 两类回调的完整交互

以下签名位置为说明占位符,必须用第 8.3 节规则计算,不是有效固定签名。平台投递 URL 是客户配置的地址。

回执通知:

POST /webhooks/sms/receipt HTTP/1.1
Host: customer.example.com
Content-Type: application/json
X-Event-Id: evt_receipt_example001
X-Event-Type: receipt
X-Timestamp: 1789344000
X-Signature: sha256=<本次原始请求体的有效签名>

{
  "eventId": "evt_receipt_example001",
  "eventType": "receipt",
  "occurredAt": "2026-09-14T02:00:03.100Z",
  "data": {
    "messageId": "MSG-22222222-2222-4222-8222-222222222222",
    "gatewayMessageId": "example-gateway-id",
    "phoneNumber": "13800138000",
    "receiptStatus": "delivered",
    "rawStatus": "DELIVRD",
    "errorCode": null,
    "deliveredAt": "2026-09-14T02:00:03.000Z"
  }
}

上行通知:

POST /webhooks/sms/uplink HTTP/1.1
Host: customer.example.com
Content-Type: application/json
X-Event-Id: evt_uplink_example001
X-Event-Type: uplink
X-Timestamp: 1789344000
X-Signature: sha256=<本次原始请求体的有效签名>

{
  "eventId": "evt_uplink_example001",
  "eventType": "uplink",
  "occurredAt": "2026-09-14T02:05:00.100Z",
  "data": {
    "applicationId": "application-example",
    "phoneNumber": "13800138000",
    "destId": "106900000000",
    "content": "收到,谢谢",
    "receivedAt": "2026-09-14T02:05:00.000Z",
    "uplinkMessageId": "uplink-example-001"
  }
}

两类通知均在验签并可靠保存后确认:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

OK

若接收端暂时无法可靠保存,可返回如下失败(正文由客户自定义,不是平台统一错误格式):

HTTP/1.1 503 Service Unavailable
Content-Type: text/plain; charset=utf-8

temporarily unavailable

预期会进入可重试分支,但当前自动重试缺陷仍未修复,不能仅凭返回503就假设平台一定补投。验签失败可返回401/403,这类响应通常不自动重试;应排查密钥、时间戳和原始字节。

9. 常见错误与处理

由业务接口异常过滤器处理的错误,通常使用 application/problem+json

{
  "type": "https://cmpp-platform.local/problems/signature_invalid",
  "title": "UNAUTHORIZED",
  "status": 401,
  "code": "SIGNATURE_INVALID",
  "detail": "请求签名校验失败"
}

以 HTTP 状态和 code 做程序判断,detail 供人阅读;type 当前是标识符,不是可访问的帮助链接。代理、网络或 JSON 解析层也可能返回其他格式,应先检查 Content-Type,不要直接假设每次失败都能解析 JSON。

HTTP code 处理方法
400 PARAMETER_INVALID / LIMIT_INVALID 检查字段类型、长度和正整数分页
409 REQUEST_REQUIRES_REVIEW 提供requestId核对,不更换幂等键重发
400 MOBILE_INVALID / CONTENT_REQUIRED 检查单个手机号和正文
400 IDEMPOTENCY_KEY_INVALID 补齐有效幂等键,检查长度与字符
400 TIME_RANGE_INVALID / TIME_RANGE_TOO_LARGE 检查时间格式、先后顺序和跨度
413 PAYLOAD_TOO_LARGE JSON请求体超过2 MiB限制,减少内容后再提交
415 UNSUPPORTED_MEDIA_TYPE 检查请求体编码及字符集,使用UTF-8 JSON
400 CURSOR_INVALID 使用平台返回的 cursor,保持筛选条件一致
401 AUTH_HEADERS_MISSING 补齐四个鉴权头
401 CREDENTIAL_INVALID 核对应用、Access Key、有效期和吊销状态
401 NONCE_INVALID / NONCE_REPLAYED 检查格式,每次调用生成新的 nonce
401 TIMESTAMP_EXPIRED 使用秒级时间戳并同步时钟
401 SIGNATURE_INVALID 核对 Secret、五行原文、路径、原始 body 和 GET 兼容摘要
403 HTTP_API_DISABLED 应用未启用或未开通 HTTP,联系平台
403 IP_NOT_ALLOWED 核对调用机器公网出口 IP 和 HTTP 白名单
403 SEND_NOT_ENABLED / MESSAGE_QUERY_NOT_ENABLED / UPLINK_QUERY_NOT_ENABLED 联系平台开通对应子能力
404 MESSAGE_NOT_FOUND / UPLINK_NOT_FOUND 核对编号及凭据所属应用
409 IDEMPOTENCY_CONFLICT 同键 body 不一致,停止盲重试并核对原请求
409 REQUEST_PROCESSING 查询既有业务结果;长期停留联系平台
409 CLIENT_MESSAGE_ID_CONFLICT 查询已有客户编号对应记录,避免重复业务发送
422 SEND_REJECTED 正文、审核或其他业务条件不满足,按说明处理,不盲重试
429 QPS_LIMIT_EXCEEDED 应用共享限流,降低并发、延后并加随机退避;新凭据不能扩大应用额度
5xx REQUEST_FAILED / INTERNAL_ERROR 保留请求标识和时间联系平台;发送场景先查询,避免重新创建业务

表格覆盖明确的常见分支,不穷尽发送链下游的所有错误。当前 5xx 文案/首次与重放错误码尚未完全统一,不依赖内部错误文本写业务逻辑。发生连接中断时可能没有任何 HTTP 状态或 JSON,按网络错误处理。

每次交互响应头 X-Request-Id 用于定位本次调用;发送受理正文中的 requestId 对应稳定业务幂等记录,两者含义不同。未知错误固定为 INTERNAL_ERROR,不返回依赖异常正文。

提交排障信息时提供:应用名称、请求时间与时区、接口路径、HTTP 状态、业务 code、requestId/messageId/clientMessageId 或 eventId。手机号和正文按需要脱敏,不提供 Secret 或完整鉴权头。

9.1 公共鉴权失败

四个接口都可能在进入业务处理前返回以下响应:

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json; charset=utf-8

{
  "type": "https://cmpp-platform.local/problems/signature_invalid",
  "title": "UNAUTHORIZED",
  "status": 401,
  "code": "SIGNATURE_INVALID",
  "detail": "请求签名校验失败"
}

其他公共失败见第 9 节。返回体的业务码和说明用于展示当前常见分支;真实网关错误可能不是该格式。

10. 当前版本的接入边界

事项 当前说明
接口定义 当前环境的 OpenAPI JSON 提供请求参数与响应结构,可用于生成客户端;本文补充接入步骤和业务含义
GET 空体 当前须使用 {} 摘要;后续变更需确认兼容策略
回调重试 暂时失败按第 8 节规则重试,接收端按 eventId 去重
超时与幂等恢复 受理快照与消息/待办同事务;结果无法确定时返回 REQUEST_REQUIRES_REVIEW,不能换幂等键绕过
参数与响应模型 clientMessageId为string/null且最长128limit为正整数,按应用上限裁剪;上行不返回内部通道和匹配字段
性能 以应用配置限制请求频率;QPS 配置不是对全链路送达能力的承诺
示例用途 示例使用演示数据;实际接入需使用本应用凭据并重新生成时间戳、nonce 和签名

11. 版本与兼容说明

本次保持v1四个业务路径、五行LF签名、无体GET兼容摘要和请求唯一标识规则。clientMessageId必须为字符串或null、最长128字符;limit必须为正整数。上行列表与详情只输出公共业务字段,详情可含所属企业和应用ID,不再输出通道或内部匹配信息。

回调是至少一次投递,请按eventId去重。查询和回调的字段、类型以当前环境提供的OpenAPI JSON为准;接入前完成签名校对,并保存requestId和messageId以便排障。