PAYATHON 2026

支付宝身份验证成功后收不到异步通知怎么办?

支付老李

结论

“应用网关可以访问”不能证明身份认证通知能够送达。alipay.user.certify.open.notify.completed 使用的是消息服务 HTTP 订阅链路。排查时,应重点确认消息订阅地址是否已发布生效、认证请求与订阅是否属于同一 app_id 和环境,以及支付宝是否实际产生了该事件。

建议先通过认证结果查询接口确认认证状态,再按”事件是否产生 → 是否发起投递 → 网关是否收到请求 → 验签和响应是否正确”的顺序排查。

重点排查项

1. 区分应用网关和消息订阅地址

应用网关通常用于接收支付宝主动调用应用接口的请求,消息服务的 HTTP 通知则使用消息订阅配置中的接收地址。两者可以使用同一个 URL,但配置入口和生效机制不同。

需要确认:

  • alipay.user.certify.open.notify.completed 已添加到当前应用的消息订阅中。
  • 订阅方式是 HTTP。
  • HTTP 接收地址已经保存并发布,并非仍处于草稿或待生效状态。
  • 配置属于当前认证业务实际使用的应用,而非另一个测试应用或关联应用。
  • 沙箱和生产环境没有混用。

如果配置后台提供”测试推送”或”连通性测试”,可以先执行测试。如果测试消息也收不到,应先检查订阅配置、域名解析、防火墙和接入层日志,不必急着排查认证业务代码。

2. 核对发起认证的 app_id

通知通常按产生业务事件的应用投递。常见问题有:

  • SDK 配置的 app_id 与消息订阅所属应用不一致。
  • 沙箱认证请求对应了生产环境的订阅地址,或者反过来。
  • 在服务商模式下,对实际调用方应用、授权商户和订阅应用之间的关系判断有误。
  • 多套环境共用一个域名,但请求被路由到了错误的服务实例。

发起认证时,建议记录以下信息:

app_id
certify_id
请求环境
业务流水号
初始化时间
认证完成时间

收到消息后,再记录通知中的 app_id、事件类型和业务内容,以便逐笔核对。

3. 确认事件是否满足触发条件

前端显示”认证完成”或用户已经跳转回业务页面,不一定表示服务端已经生成了可投递的认证完成事件。用户中途退出、关闭页面、流程超时或最终状态尚未确定,都可能造成前后端状态不一致。

应使用与当前产品接入方式匹配的认证结果查询接口,根据 certify_id 查询服务端最终状态。具体接口名称和返回字段以当前支付宝开放平台文档及应用已开通的产品能力为准,不能只看前端跳转结果。

建议分别测试以下情况:

  • 认证成功;
  • 认证失败;
  • 用户主动退出;
  • 流程超时;
  • 同一 certify_id 重复进入认证页面。

不同状态是否都会产生该事件,可能受产品能力和事件定义限制,应以当前控制台中的事件说明为准。

4. 检查公网入口是否真的收到 POST 请求

浏览器可以访问,只能说明 GET 请求可能正常,不能证明支付宝发送的 POST 请求能够到达应用。

可以使用 curl 模拟 POST:

curl -i -X POST 'https://example.com/alipay/notify' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'app_id=test' \
  --data-urlencode 'msg_method=alipay.user.certify.open.notify.completed' \
  --data-urlencode 'biz_content={"certify_id":"test"}'

重点检查:

  • URL 是否允许公网直接访问;
  • 是否支持 POST;
  • 是否发生 301、302 或 307 跳转;
  • HTTPS 证书链、域名和有效期是否正常;
  • WAF、防火墙或 API 网关是否拦截支付宝请求;
  • 是否要求登录、Cookie、Token、验证码或 CSRF Token;
  • 是否存在 IP 白名单限制;
  • 反向代理是否将请求转发到正确的服务和端口;
  • 接入层是否限制了请求体大小、请求方法或 Content-Type;
  • 服务端是否在读取请求前就返回了 401、403、404 或 500。

建议同时查看 CDN、负载均衡、Nginx、应用网关和业务应用日志。如果所有入口日志中都没有对应请求,问题更可能出在订阅配置、应用环境或支付宝投递链路。如果入口有请求,但业务日志没有记录,则要检查内部路由和中间件。

通知处理示例

以下 Java 示例展示了一种通用处理方式。实际参数名、验签方法和成功响应格式,必须以当前消息服务文档及所用 SDK 版本为准。

@PostMapping(
    value = "/alipay/notify",
    consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public ResponseEntity<String> handleNotify(
        HttpServletRequest request) throws Exception {

    Map<String, String> params = new HashMap<>();

    for (Map.Entry<String, String[]> entry
            : request.getParameterMap().entrySet()) {
        params.put(entry.getKey(), String.join(",", entry.getValue()));
    }

    boolean valid = AlipaySignature.rsaCheckV1(
        params,
        alipayPublicKey,
        StandardCharsets.UTF_8.name(),
        "RSA2"
    );

    if (!valid) {
        return ResponseEntity.badRequest().body("failure");
    }

    String appId = params.get("app_id");
    String msgMethod = params.get("msg_method");
    String notifyId = params.get("notify_id");
    String bizContent = params.get("biz_content");

    if (!expectedAppId.equals(appId)) {
        return ResponseEntity.badRequest().body("failure");
    }

    if (!"alipay.user.certify.open.notify.completed".equals(msgMethod)) {
        return ResponseEntity.ok("success");
    }

    // 应使用数据库唯一约束或幂等表防止重复处理
    if (!notifyRepository.existsByNotifyId(notifyId)) {
        notifyRepository.save(notifyId, bizContent);

        // 建议进入队列或异步任务,不要在通知线程中执行耗时业务
        certificationService.processAsync(bizContent);
    }

    return ResponseEntity.ok("success");
}

如果接收端使用 Spring Security,需要允许匿名访问通知路径,并根据实际安全方案处理 CSRF:

http.authorizeHttpRequests(auth -> auth
        .requestMatchers("/alipay/notify").permitAll()
        .anyRequest().authenticated()
    )
    .csrf(csrf -> csrf
        .ignoringRequestMatchers("/alipay/notify")
    );

允许匿名访问通知路径,不代表可以跳过签名校验。公网可匿名访问和验签必须同时满足。

验签时容易出现的问题

验签应使用支付宝公钥,或者应用公钥证书体系中对应的支付宝公钥证书,不能误用应用私钥。还需要检查:

  • sign_type 是否与验签配置一致;
  • 字符集是否一致,通常为 UTF-8;
  • 验签前是否修改、转义或重新序列化了参数;
  • 是否遗漏参与验签的请求参数;
  • 是否先解析 biz_content,再将其拼回去验签;
  • 网关或框架是否改变了参数编码;
  • 使用证书模式时,是否正确处理了证书序列号和公钥证书。

建议保留原始请求体和解析后的参数,但日志必须脱敏,避免记录姓名、证件号码、人脸数据或其他敏感信息。

正确响应通知

通知处理完成后,应按照消息服务协议返回规定的成功响应。很多支付宝异步通知使用纯文本:

success

具体格式仍以当前事件订阅文档为准,不要自行返回 JSON、HTML 页面或重定向地址。

接收接口应尽快完成验签和幂等落库,然后返回结果。耗时业务应交给消息队列或异步任务处理,避免因接口超时触发重复投递。

还要注意:

  • 异步通知可能重复到达,业务处理必须幂等;
  • 不要根据通知到达顺序判断最终状态;
  • 返回 HTTP 200 不一定代表业务确认成功,响应正文也可能需要符合协议;
  • 首次通知被拒绝后,平台是否重试以及重试周期如何,应以平台当前规则为准。

推荐的排查顺序

  1. 使用认证结果查询接口确认该 certify_id 的服务端最终状态。
  2. 核对认证调用使用的 app_id、环境与订阅应用是否一致。
  3. 确认事件订阅已经发布生效,而非只完成了勾选或保存。
  4. 使用控制台测试推送验证消息接收地址。
  5. 查看公网入口、网关和应用日志,确认是否收到 POST 请求。
  6. 如果收到请求但处理失败,检查路由、CSRF、验签、超时和响应正文。
  7. 完整记录 certify_id、通知标识、请求时间和响应结果,排除重复通知或关联错误。
  8. 如果认证已经成功,应用、环境和订阅配置也都正确且已生效,但平台侧仍没有任何投递记录,应携带 app_id、certify_id、认证完成时间及订阅配置截图,联系支付宝技术支持核查事件生成和投递记录。

业务上不要只依赖异步通知。更稳妥的处理方式是”异步通知实时更新 + 查询接口定时补偿”。长时间未收到通知时,使用已保存的 certify_id 主动查询并对账,避免因网络抖动或通知处理失败,导致状态一直停留在处理中。

备注:内容仅供参考。