TextPulse

SMS WEBHOOK CALLBACK

SMS Webhook回调:可靠接收短信状态和上行消息的工程实践

Webhook允许短信平台在消息状态变化时主动通知企业系统,例如短信已送达、发送失败、过期或收到用户回复。一个可靠的回调服务必须能够验证来源、快速响应、处理重复和乱序事件,并把状态安全地关联到原始业务请求。

传输方式 HTTPS POST事件通知
关键设计 验签、幂等、快速响应
异常处理 重复、乱序、重试、积压
核心记录 业务ID、message ID、状态和时间

一、Webhook在短信系统中的位置

企业调用短信API后,接口通常只返回“请求已接收”或“提交成功”,最终是否送达需要等待后续状态。Webhook把状态变化主动推送到企业指定的HTTPS地址,避免业务系统持续轮询。

1业务系统提交短信并保存业务流水号。
2短信平台返回message ID。
3运营商产生送达或失败状态。
4平台向Webhook端点发送事件。
5企业系统验签、去重并更新状态。

除送达状态外,Webhook还可用于上行短信、退订、号码回复或账户事件。不同事件应使用明确的类型字段,不要依赖自由文本猜测事件含义。

二、回调端点的基本要求

  • 必须使用HTTPS,并配置有效证书和稳定域名。
  • 只接受需要的HTTP方法和内容类型。
  • 限制请求体大小,校验JSON结构和必填字段。
  • 先完成必要校验并入队,再快速返回成功状态。
  • 不要在同步请求中执行耗时的报表、通知或第三方调用。
  • 记录请求时间、来源、事件ID和处理结果。

回调端点的目标不是一次完成所有业务逻辑,而是安全、快速地接收事件。复杂处理应进入内部消息队列,由后台任务异步执行。

三、建议的事件数据结构

下面是通用示例,实际字段名称应以平台文档为准:

{
  "event_id": "evt_01J...",
  "event_type": "sms.delivery.updated",
  "message_id": "msg_01J...",
  "client_reference": "order_20260716_001",
  "status": "delivered",
  "occurred_at": "2026-07-16T06:30:15Z",
  "destination": "+国家码手机号",
  "error_code": null
}

event_id用于事件去重,message_id关联短信平台记录,client_reference关联企业内部业务,occurred_at用于判断事件顺序。手机号等敏感字段应按最小需要保存和展示。

四、签名验证与来源认证

Webhook不能只依赖来源IP判断真实性。常见做法是平台使用共享密钥对“时间戳+原始请求体”计算HMAC签名,企业系统收到请求后使用相同算法重新计算并进行恒定时间比较。

# 通用Python示例,头名称和签名格式需按实际平台调整
import hashlib
import hmac

signed_payload = timestamp.encode() + b"." + raw_body
expected = hmac.new(
    WEBHOOK_SECRET.encode(),
    signed_payload,
    hashlib.sha256
).hexdigest()

if not hmac.compare_digest(expected, received_signature):
    raise PermissionError("invalid webhook signature")
安全提示:必须使用原始请求体计算签名,不能先解析JSON再重新序列化,否则空格和字段顺序变化可能导致验签失败。

还应检查时间戳是否在允许窗口内,防止攻击者重复发送历史有效请求。密钥应保存在服务端密钥管理系统,并支持轮换。

五、幂等、重复与乱序事件

Webhook平台通常采用“至少一次”投递,因此同一个事件可能重复到达。数据库应对事件ID建立唯一约束,已处理事件直接返回成功,不能重复执行退款、通知或业务操作。

事件也可能乱序到达,例如“delivered”先于“sent”。更新状态时应结合状态优先级和事件发生时间,避免较旧事件把最终状态覆盖成中间状态。

问题推荐处理
重复事件使用event ID唯一索引,重复请求返回成功
乱序事件比较occurred_at及状态优先级
未知message ID暂存事件并告警,不直接丢弃
数据库暂时不可用返回可重试状态,并保护服务不过载

六、HTTP响应与重试策略

完成验签和可靠入队后,应尽快返回2xx。格式错误、签名错误等永久问题不应通过无限重试解决;系统临时不可用则可以返回5xx,让平台稍后重试。

企业应确认平台的重试次数、退避时间和最长保留期。自己的处理服务也需要死信队列,用于保存多次失败的事件,避免异常数据永久丢失。

推荐:监控非2xx比例、平均响应时间、重试数量、积压事件和死信数量,并设置明确告警阈值。

七、日志、数据和监控

  • 保存event ID、message ID、业务流水号和事件类型。
  • 保存接收时间、发生时间、处理时间和最终结果。
  • 记录签名验证结果,但不要在日志中输出密钥。
  • 对手机号等敏感字段进行掩码或限制访问。
  • 建立回调延迟、失败率、重复率和积压量仪表盘。
  • 定期演练密钥轮换、数据库故障和消息队列积压。

常见问题

Webhook收到重复回调是否表示平台异常?

不一定。可靠事件系统通常采用至少一次投递,网络超时会导致平台无法确认企业是否成功接收,因此企业端必须实现幂等。

Webhook应该先更新数据库还是先返回200?

至少要先完成验签并把事件可靠写入数据库或消息队列,再返回成功。不能在数据尚未保存时提前确认。

可以只通过IP白名单保护Webhook吗?

不建议。IP白名单可以作为附加措施,但核心仍应是HTTPS、签名验证、时间戳检查和密钥管理。