接入教程2026-11-02· 12 分钟阅读

短信带链接怎么发:链接报备、签名重算与动态链接边界的工程做法

短信带链接的三个真问题:链接应写进 code 还是 title(两者一个参与签名一个不参与)、改链接要不要重新报备(要,人工终审)、签名与表单编码的先后顺序。另含 1002/1004/1005/1008/1009 排查表与 11 条上线检查单。

诺
诺正通研究院

短信带链接,最常见的误解是"这是个短链生成问题"——找一家短链服务、生成一个链接、塞进短信就完事。

真正卡住接入的是另外三件事,而且没有一件跟短链服务有关:

  • 链接该写进接口的哪个字段? 短信接口没有 link 也没有 url 参数,能放链接的地方只有两个,而这两个位置在签名和报备上的后果完全相反。
  • 改一个链接要不要重新报备? 要,而且是人工终审,1-3 个工作日。
  • 改一个链接要不要重算签名? 取决于你把它写在哪。
  • 大部分人在第 1 步就把链接塞进了正文,然后发现线上开始报签名错误;或者链接改了、签名重算了,发出去却没有短信。这篇只讲这三件事,以及它们背后的同一个判断:链接不是一个字段,是一个会同时触发签名链和报备链的改动。

    一、先看接口:没有 link 字段,只有两个落点

    短信通道的请求体一共 5 个参数,其中能装下链接文本的只有两个:

    参数含义必填参与签名能放链接
    mall_id商户 ID是是否
    phone接收手机号是是否
    code短信内容(正文)是是能
    title模板固定内容见下方说明否能
    sign请求签名是—否

    签名算式与核验类接口不是一套体系:

    sms 通道:   sign = md5(mall_id + phone + code + appkey)      ← 没有 tm
    

    核验类通道: sign = md5(mall_id + realname + idcard + tm + appkey)

    请注意 title 不在这条算式里。 这是本篇所有结论的起点。

    另外两个必须先知道的接口事实:

  • 短信通道走 http://api.nztapi.com/sms.php(注意是 http),核验类走 https://api2.nztapi.com。短信密钥和核验密钥不要在同一个客户端配置里复用——地址、签名算式、返回结构三样都不一样。
  • 返回是扁平结构,没有核验类那种 data / status 包装:
  • { "code": 1000, "message": "发送成功" }

    一个文档层面的矛盾

    title 在参数表里标注为"必填",同一行的说明却写着"模板固定内容,可为空";而 OpenAPI 规格(nztapi.yaml)的 required 列表是 [mall_id, phone, code, sign]——里面没有 title。

    两处对不上,所以联调时别猜:传空 title 试一次,看通道是否接受。如果接受,说明它确实可空;如果报参数错,那就是必填,只是 OpenAPI 规格漏写了。两种情况都不影响你把链接放进 code,但会影响你怎么组织模板。

    二、链接写 code 还是写 title:三条判据

    判据写进 code(正文)写进 title(模板固定内容)
    签名参与签名 → 链接一变,签名必须重算不参与签名 → 链接变,签名不动
    报备模板内容变更 → 通常要重新报备模板固定内容变更 → 通常要重新报备
    适用每个用户/每批活动用不同落地页链接固定、同一落地页批量发送

    两条都逃不掉报备,但只有一条逃不掉签名。这就是"两条独立成本线"的由来。

    再往下一层,有个实操上的细节经常被忽略:code 这个字段会同时承担"固定内容"和"变量内容"两种角色。

    比如验证码短信,正文是"点击 https://短域名/a 领取验证码 123456"——链接是固定的,验证码是变量。链接一旦写在 code 里,报备的模板正文里就必须出现这条具体链接;变量部分还要另外满足占位符规则。也就是说,你没法靠"这字段是变量位"来回避链接报备。

    反过来,如果把链接放进 title,title 本身就是模板的固定部分,链接在报备模板里的位置是明确的,同样要逐条报备。区别只在于签名。

    三、坑 1:链接进了签名,code 的字符一个都不能差

    签名算式里的 code 参与哈希,也就是说最终发送出去的每一个字符都会改变签名结果。

    最容易踩的顺序错误是"先编码、再算签名":

    # ❌ 错:先 URL 编码,再拿编码后的串算签名
    

    code_encoded = urllib.parse.quote(code) sign = md5(f"{mall_id}{phone}{code_encoded}{appkey}") requests.post(url, data={"code": code_encoded, "sign": sign})

    问题:表单提交时 requests 会对 code_encoded 再编码一次,

    服务端解出来的是 code(未编码版),与签名时的串不一致 → 1002

    ✅ 对:先定稿最终正文,用同一份原文算签名,编码交给表单层

    code = f"【诺正通】点击 {link} 完成验证,5 分钟内有效。" sign = md5(f"{mall_id}{phone}{code}{appkey}".encode("utf-8")).hexdigest() requests.post(url, data={"mall_id": mall_id, "phone": phone, "title": "验证码", "code": code, "sign": sign})

    铁律:签名用的串和发送出去的串,必须是同一个字符串对象的内容。 只要中间做过任何一次转义、拼接、模板渲染后的二次处理,就可能出现签名对不上。

    还有一个更隐蔽的变体:链接里带了 &、=、中文或空格。表单编码时这些字符的处理方式和签名时用的原文对不上,同样表现为签名错误。

    建议:短链路径只用 [A-Za-z0-9-] 这类字符。域名可以自定义、也可以用短域名,但路径部分越简单,签名与编码越不容易打架。这不是为了好看,是为了让签名可复现。

    四、坑 2:报备要落到具体链接 + 不支持动态,两条规则叠在一起才是难点

    把两条业务规则放在一起看:

  • 规则一:报备要落到具体的链接,不是一个域名下的任意链接。
  • 规则二:不支持动态生成链接,不能按用户、按订单实时拼。
  • 单看每一条都不算复杂,叠在一起推出的结论才是关键:

    短信正文里能出现的链接,是一个需要逐个报备的有限集合。

    这意味着规模是可以推演的:一次活动一个新落地页 = 一条链接 = 一次报备 = 运营商终审 1-3 个工作日(部分省份还有额外审核环节)。如果链接按用户维度变化,报备条数就发散,等于永远发不出去。

    所以正确的架构是:把"每条消息都不同的东西"从链接里拿出去。

    做法链接是否逐条不同报备条数评价
    链接里带 user_id每个用户一条发散走不通
    链接固定(已报备的那一条),个性化在落地页侧识别一条1可行
    短域名做固定入口,短链后台映射到真实落地页一条(短信里那一条)1可行,且换落地页不用重新报备

    第三行值得多说一句:改短链后台的映射关系,短信里的链接没变,报备就不用动。 这正是短域名在带链接短信里的真正价值——它把"要报备的东西"和"真正要去的地方"解耦了。

    顺带划清一条能力边界:短链服务本身不在接口范围内——我们的接口文档里没有短链 API,短链与跳转需要你自己搭或用第三方。我们提供的是"短信正文可以带链接"这一层能力,以及带链接短信的通道、签名与报备支持。

    五、坑 3:改链接要付两笔钱

    把成本拆开看,决策就清楚了:

    你改了什么签名要不要重算报备要不要重做生效时间
    链接路径(换落地页)写在 code 里 → 要要运营商终审后
    链接域名写在 code 里 → 要要运营商终审后
    只改短链后台的跳转映射不用不用立即
    只改正文里的固定文案要要运营商终审后
    链接写在 title 里,改链接不用要运营商终审后

    结论:一次报备能覆盖的,是"同一条链接 + 同一套正文"的全部发送。 要发不同的链接,就得再报备一次。

    很多团队只算了签名这一笔(改完代码、重算签名、以为完事),结果线上静默不发——通道侧对未报备内容直接拦,用户收不到,接口还可能返回成功。这一条是带链接短信最典型的"以为做完了其实没做"的场景。

    六、失败排查:按错误码定位

    现象原因怎么查
    返回 1002 签名错误,核验类接口却正常签名函数复用了核验类的拼接顺序(多了 tm,或漏拼 code)把签名函数按接口分开实现,不要写死字段列表
    返回 1002,正文一个字没改却偶发失败签名用的串和实际发送的串不是同一份(编码时机、模板二次渲染)打印签名时的 code 与发出去的 code,逐字符比对
    返回 1004 短信内容为空code 传空(把内容全放进 title 也不行,code 本身不能空)检查正文组装逻辑
    返回 1005 敏感词短链路径或正文文案命中审核短链路径改成无意义的短码,正文措辞复查
    返回 1003 可发送条数为 0通道内可发余额或配额为 0不是参数问题,查额度
    返回 1008 频率过快同一号码短时间多次请求业务侧加频控与倒计时
    返回 1009 短信内容缺少签名信息正文没带【签名】正文结构必须是 【签名】+ 内容
    报备已过,用户仍收不到运营商侧拦截;或通道自动重发与业务层重发叠加造成对账错位见短信验证码被刷怎么防一文的排查顺序
    返回 1000,用户说链接点不动链接所在域名未解析、HTTPS 证书异常,或短链后台映射未配先在浏览器里打开那条链接本身

    七、合规:带链接的短信更容易踩线的地方

  • 退订方式必须在正文里明示,不能只放在落地页或藏在链接后面。营销短信带链接时,用户点进来才知道怎么退订,已经晚了一步——这是投诉的直接来源。规则细节见营销短信合规指南。
  • 落地页与短信文案要对得上。短信里写"领取优惠"、点进去是别的内容,性质上比不带链接更严重——带链接提供了直达通道,差异更容易被认定为主动误导。
  • 链接里不要带明文敏感信息(手机号、证件号、验证码、订单号)。链接会被转发、会进聊天记录。敏感参数应该在落地页侧凭登录态或短链映射去取,而不是写进 URL。
  • 涉及的法规边界是《通信短信息服务管理规定》(2015 年施行)、《广告法》与《个人信息保护法》(2021 年施行)这几部;工程上真正要落到设计上的,是授权、退订、明示与最小必要这几条。
  • 八、上线检查单

    □ 本批次一共要发几条不同的链接?(这就是报备条数,先数清楚)
    

    □ 每条链接都已完成报备,并确认运营商终审通过 □ 链接域名能正常解析、HTTPS 正常、落地页与文案一致 □ 短链路径只用 [A-Za-z0-9-],不含中文/空格/&/= □ 签名用最终 code 原文计算,URL 编码交给表单层,不提前编码 □ title 的必填性已实测确认(文档表与 OpenAPI 规格不一致) □ 业务层不叠加自动重发,避免与通道自动重发重复计费 □ 短信接口只从服务端调用(地址是 http,别在客户端直连) □ 个性化参数放在落地页/短链映射侧,不拼进短信里的链接 □ 正文里明示退订方式 □ 密钥分环境隔离,日志脱敏(短信正文与短链不落日志明文)

    相关阅读

  • 短信验证码 API:三网合一通道、变量短信、实时状态回执,短信带链接能力的接入入口
  • 短链服务怎么设计:302 跳转、过期策略与点击追踪:链接批准之后,承载链接的短链服务怎么设计、点击数据从哪来
  • 变量短信与长短信怎么发:占位符规范、拆分计费与对账口径
  • 短信签名模板报备流程:签名与模板报备的完整流程、5 类常见驳回
  • 消息通知接口接入指南:签名规则、模板报备与发送频率控制的接入视角
  • 短信验证码被刷怎么防:带链接短信同样怕刷,接口层的三个坑
  • 营销短信合规指南:带链接营销短信的授权、退订与内容边界
  • API 签名机制一篇讲透:md5 签名与字段拼接的安全原理
  • 短信通道按量计费、签名与模板需报备,具体与商务确认;新用户有免费试用额度可用于联调。
    #短信带链接#短信短链#模板报备#签名机制#短信营销

    看完想立刻接入?

    100 次免费调用,1 个工作日开通

    限时新用户 100 次免费

    1 个工作日开通 · 7×24 技术支持