X Chat 私信失败排查:登录状态、密钥版本与消息签名
X 账号能够登录,并不意味着每个加密会话都已经可用。账号访问权限、正确的会话密钥和有效的消息签名,是不同的条件。排查应先确定失败发生在认证、加密消息准备、发送请求,还是消息进入业务系统的环节。仅凭“发送失败”,无法判定是签名错误、限流或账号受限。
先确认这四件事
- 是一个会话失败,还是整个账号异常?问题出现在发送、接收,还是两者都有?
- 对于加密消息,要检查对应的会话密钥版本,不能认为缓存里有密钥就足够。
- 区分 X Chat 消息签名与 UnifyPort Webhook 验签。
- 在重试或调整账号连接前,先保留请求标识和发生时间。
登录状态与聊天密钥各自负责什么
X 官方加密说明区分了身份密钥、签名密钥和带版本的会话密钥。可以据此建立排查模型:
| 材料 | 职责 | 应检查的问题 |
|---|---|---|
| 账号会话 | 访问账号 | 是否为预期账号,会话是否可用? |
| 身份私钥 | 解开分发给该用户的会话密钥 | 是否具备匹配的身份密钥材料? |
| 签名私钥 | 为消息及支持的状态变更签名 | 是否选用了正确的签名密钥和版本? |
| 会话密钥 | 加密或解密会话内容 | 是否具备这条消息所需版本的密钥? |
这些检查发生在不同层。使用托管连接器的应用开发者通常检查公开账号状态和错误响应,由连接器维护者继续排查协议密钥。API 认证通过,不能证明后面的检查也已通过。
用于恢复聊天密钥的 Chat 口令,也不同于 API key 或账号登录凭据。恢复失败时,应沿用账号所有者已有的 Chat 设置。不要把重置口令当作普通重试:X 的 Chat 帮助文档说明了口令不可用时恢复加密历史记录的限制。
改动配置前,先缩小失败范围
保留一次失败请求,并与已知正常的调用对比。例如,用户资料查询正常,但某个会话发送失败,应优先调查该会话,不能直接认定整个服务不可达。这样的对比只能缩小范围,还不能确定根因。
| 症状 | 保留什么证据 | 下一步 |
|---|---|---|
| 无法访问账号 | 认证响应、账号及运行状态 | 按当前授权文档处理账号访问问题。 |
| 只有一个会话发送失败 | 请求 ID、会话 ID、准确的错误类别 | 由连接器维护者检查会话状态、密钥版本、token 和签名输入。 |
| 部分消息无法解密 | 受影响的消息 ID,以及可获得的密钥版本 | 检查对应历史版本的密钥是否存在。 |
| 发送结果不确定 | 请求时间、响应或超时、接收端结果 | 先核对这次发送,再决定重发;超时可能留下未知结果。 |
| X 收到消息,业务系统没收到 | Webhook 配置、投递尝试、接收端响应 | 单独排查事件投递,不与 Chat 加密混在一起。 |
如果直接使用 X Chat SDK,X 官方排障指南覆盖了密钥初始化、会话缺钥、解密及签名失败。文中的 SDK 方法和错误消息不等同于 UnifyPort 的公开 API 契约。
缺钥之后,需要明确恢复和失败处理
对连接器实现而言,一条可采用的恢复路径是:
- 确认会话和事件要求的精确密钥版本。
- 查询该版本的缓存。
- 通过当前集成支持的恢复路径获取被封装的密钥材料。
- 校验材料,使用对应身份私钥解开,并保存到版本缓存。
- 重新读取缓存,在有界重试策略内恢复受影响的操作。
这是实现思路,不代表每个已连接账号都能恢复所有历史消息。恢复请求成功后,如果目标密钥仍不存在,就不能宣称恢复完成。同一密钥的并发请求可以共享恢复工作,但每条消息的处理结果仍要分别记录。旧密钥可以保留用于解密历史消息,同时不应覆盖较新的默认密钥。
收消息与发消息的失败处理也不同。接收侧应在支持的重试窗口内保留原始事件,不能把密文当作已解密消息。发送侧则需要明确:恢复失败是向调用方报错,还是允许现有回退路径。如果回退改变了加密属性,就不能把它描述为等价的加密投递。两条路径都不应无限重试。
签名问题需要签名层的证据
只有上游响应确实指出签名失败时,维护者才应针对性检查签名密钥选择、发送者身份、密钥版本以及参与签名的准确字节。重复提交相同的无效载荷,不会纠正这些输入;关闭验签也不能修复问题。
单个 HTTP 错误或宽泛的 Provider 错误,不能证明签名失败。同样,连接器中的签名修复只能证明其实现发生了调整,不能证明 X 修改了推荐算法,也不能单凭它断定 X 最近更新了协议。
通过 UnifyPort 公开 API 排查
UnifyPort 为已连接的消息账号提供非官方接口。先对照当前 X 授权指南,再检查 GET /v1/accounts/{account_id}/auth 和 GET /v1/accounts/{account_id}。认证状态与 runtime_status 用于判断连接状态,并不是逐会话的加密健康检查。
对于已有的 POST /v1/messages 请求,可以保留精简的诊断摘要。下面的函数接收调用方已经获得的 Response,不会发送或重试消息:
async function recordMessageAttempt(response) {
const body = await response.clone().json().catch(() => null);
console.info({
observed_at: new Date().toISOString(),
http_status: response.status,
request_id: body?.request_id ?? response.headers.get('X-Request-Id'),
code: body?.error?.code,
numeric_code: body?.error?.numeric_code,
});
}
消息账号 ID、相关会话和消息 ID 应保存在限制访问的排障记录中。公开 code、numeric_code 的定义以错误参考为准。宽泛的 provider_unavailable 不会直接揭示某个 X 签名错误,也不能自行把内部 X 错误与公开错误码建立一一对应关系。
这个函数刻意省略消息正文、cookies、会话 URL、PIN 和密钥材料。如果根本没有收到 HTTP 响应,应记录客户端失败、操作和时间,此时可能没有可用的服务端请求 ID。不要生成一个 ID 并把它作为服务端证据。
Webhook 签名保护的是另一段连接
| 签名 | 验证对象 | 排查位置 |
|---|---|---|
| X Chat 消息签名 | 被签名的 Chat 事件 | X Chat 客户端或连接器协议实现 |
X-Device-Signature | UnifyPort 发给接收端的投递请求 | Webhook 接收端及其 signing_secret |
后者使用 HMAC-SHA256,对时间戳、一个句点和原始请求体进行计算,具体见 Webhook 投递参考。修复这层 HMAC 校验,并不能补齐 X 会话密钥;HMAC 验证成功,也不能证明一条出站消息已到达对方。
可按 Webhook 优先的接入检查清单核对接收配置。下游应用结构则可参考 X 私信与提及监听器实录。验证入站事件后先保存,再执行耗时的路由工作。
常见问题
重新登录一定能补齐密钥吗?
不能保证。更新会话并不证明所需身份密钥或会话密钥版本已经可用。应先定位缺失材料,再决定是否重新配置账号。
密钥恢复成功,就代表消息已送达吗?
不是。密钥可用、提交被接受、接收端收到消息、Webhook 接收服务处理完成,是不同的观察结果。需要验证业务真正关心的环节。
能直接调用 UnifyPort 的公开接口恢复 Chat 密钥吗?
本文没有引入这样的公开接口。请使用已记录的公开 API,并向支持团队提供诊断标识。内部连接器操作不能直接当作公开 API 路由使用。
能把所有 X 私信都称为加密消息吗?
不能。X 的 Chat 文档描述了未加密消息请求的场景。应先确认实际会话和发送路径,再说明其加密属性。
下一步
测试具体载荷前,先查看当前消息能力矩阵。如果直接对接 X 官方 Chat API,应使用其 SDK 和恢复文档;它的认证方式及事件契约与 UnifyPort 不同。
一手来源
核对日期:2026 年 9 月 10 日。
让消息接入变成一条稳定的产品管线。
先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。