
Webhook 推送延迟和丢包是企微对接里最影响体验的问题。用户发了消息半天没响应或者业务系统漏处理排查起来很头疼。这篇讲一套从接收到消费的完整异步方案。先搞清延迟从哪来企微 Webhook 推送链路是企微客户端 → 企微服务器 → 你的 Webhook 服务延迟主要发生在最后一跳。企微服务器要求你的服务5 秒内返回否则会判定超时并重复推送。如果你的处理逻辑重调数据库、调第三方接口、跑大模型很容易超时。超时后果不只是慢还有重复。企微会认为你没收到隔段时间再推一次。不做幂等的话同一条消息可能被处理多次。丢包的两种真相一种是真的没收到服务挂了、网络断了、回调地址配错了。这种好排查看服务日志就行。另一种更隐蔽收到了但处理失败了而你已经返回了 ok。企微认为投递成功实际上你的业务逻辑挂了。这种静默丢失最难发现。方案接收与处理分离核心思路是先存后算——Webhook 只做接收和存储业务处理交给异步 Worker企微推送 → Webhook 接口 → 写入消息队列 → 返回 ok100ms ↓ Worker 消费 → 业务处理Webhook 接口本身只干三件事验签、写队列、返回。业务逻辑全部异步。app.route(/wx-api/webhook/, methods[POST]) def webhook(): sig request.headers.get(X-Eyun-Signature) if not verify_signature(request.data, sig): return forbidden, 403 delivery request.headers.get(X-Eyun-Delivery) event request.headers.get(X-Eyun-Event) payload { delivery: delivery, event: event, data: request.json.get(data), received_at: time.time(), } queue.push(webhook_events, payload) return ok这样不管业务处理多慢Webhook 都能在 100ms 内返回。幂等去重防止重复消费队列消费端用delivery做幂等def consume(): payload queue.pop(webhook_events) key fprocessed:{payload[delivery]} if not redis.setnx(key, 1, ex86400): return # 已处理过丢弃 process(payload[event], payload[data])ex86400保留一天防止同一天重复投递。第二天同 delivery 再推可能是新事件。失败补偿机制Worker 处理失败怎么办三层兜底层级机制说明第一层队列重试消费失败重新入队延迟几秒再试第二层死信队列重试 N 次后仍失败进死信队列告警第三层定时补偿每天凌晨扫一遍死信队列人工或自动重跑死信队列一定要告警否则消息积压了都不知道。高并发下的队列选型消息量不大时用 Redis List 就够。量大了建议上专业队列队列适用场景Redis List单机、量小、简单Redis Stream需要消费组、ACK 确认RabbitMQ / Kafka高并发、多消费者、持久化要求高Redis Stream 的消费组模式很适合 Webhook 场景——多个 Worker 分摊消费每条消息有明确 ACK消费失败会留在 pending 列表。监控指标别漏异步方案上线后这几个指标要盯Webhook 响应时间超过 1 秒就要警惕队列积压量持续增长说明消费跟不上死信队列长度非零就要告警重复投递率Delivery 重复出现的比例过高说明上游有问题小结Webhook 延迟和丢包的解法是接收快、处理慢、失败可追。接收端只做验签和入队业务逻辑全部异步消费端做幂等和重试死信队列兜底并告警。这套结构里企微接口本身只是入口真正的稳定性靠你自己的异步架构。接口路径是/wx-api/api/模块/动作鉴权Bearer eyk_xxxx凭证在Eyun 平台开通实例时拿到。回调配置和事件类型在开发文档里有完整说明。