值班日记凌晨 2:17告警频道弹出一条phone_number_quality_updatequalityRating: RED。三天前刚升到 Tier 3 的印尼号码现在被 Meta 打回了 Tier 1。原因不是发送量超标而是最近的模板消息让用户点了举报垃圾信息——在你睡觉的时候WhatsApp 正在给你的号码打分。分低了不用你犯错Meta 替你关闸。1. 质量评分为什么是账号的隐形容器大多数人理解 WhatsApp Cloud API 的限制是靠显性规则——80 msg/s 吞吐量、每日模板限额、24h 客服窗口。但这些只是结果。真正决定你能发多少、发多快、以及会不会在某天醒来发现账号被禁的是一套 Meta 在后台默默运行的质量评分机制。这机制有个令人不安的特点你没法直接控制它但它的每一次波动都在改写你的业务上限。WhatsApp 端到端加密意味着 Meta 读不了你的消息内容但它有另一套办法判断你是好商家还是垃圾信息源——它看的是用户行为信号。2. 质量评分体系全景GREEN / YELLOW / RED2.1 三层评分不是静态标签评分状态含义信号特征业务影响GREEN (高)用户积极回应消息被欢迎回复率高、块率 0.5%、零举报可自动升级 Tier发送不受限YELLOW (中)出现警告信号但尚未触底块率 0.5%~2%、偶发举报、模板质量下降停止 Tier 升级需人工介入审查RED (低)大量负面反馈濒临制裁块率 2%、持续举报、模板被停用触发 FLAGGED 状态7 天内不恢复→降级或封号评分基于最近 7 天滚动窗口且近期交互权重更高。换句话说过去 6 天都 GREEN昨晚发了一波劣质营销消息导致大量举报——今天醒来可能就是 RED。一个容易被忽略的事实被用户 Block 的次数不是唯一指标。Meta 还追踪用户储存在通讯录中的比例、对话是否被静音/归档、用户是否回复、消息是否被转发等数十个隐性信号。这些信号 Meta 从不公开披露细节但工程上可以通过反向工程——观察 Webhook 反馈的速度和频次变化来推断当前评分的大致档位。2.2 评分如何影响消息层级Messaging TierTier 结构2025 年 10 月后改为 Portfolio 级不再按号码单独计算 TIER_250 → TIER_2K → TIER_10K → TIER_100K → UNLIMITED ↑ ↑ ↑ ↑ ↑ 新注册默认 需 50% 使用率 YELLOW 以上质量评分自动升级 如果评分降为 RED ──→ 停止任何 Tier 升级 ──→ 原有 Tier 可能被降级 ──→ FLAGGED 状态持续 7 天期间只能回复客户消息 ──→ 仍不恢复 → RESTRICTED → 永久封禁这张表值得贴在显示器旁边。因为一旦进入 RED → FLAGGED → RESTRICTED 这条死亡螺旋你能做的只有一件事等。等 7 天 Meta 重新评估期间不能发任何主动消息。3. 评分信号拆解Meta 到底在看什么3.1 正向信号拉升评分信号类型来源工程意义用户回复消息Webhookmessages事件表示对话有价值是 Meta 奖励力度最大的信号用户将号码存入通讯录无直接事件但影响评分存入即表示这不是垃圾号码模板消息获得高点击Interactive 按钮点击回调表示内容有用持续的双向会话多次messages事件间隔短深度互动权重大3.2 负向信号拖垮评分信号类型严重程度恢复时间用户 Block 号码⚠️ 中等7 天窗口内自然过期举报垃圾信息 严重可能立即使模板 PAUSED模板质量评分 RED 严重第 1 次暂停 3h→第 2 次暂停 6h→第 3 次永久禁用连续发送无互动⚠️ 中等需人工调整发送策略发送到非 Opt-in 用户 严重举报率飙升恢复极慢3.3 工程设计块率实时监控想在 RED 出现之前知道趋势你可以从 Webhook 的status事件中反向推导块率——虽然没有直接的 user_blocked 事件但你可以监控已发送但从未 delivered 的消息比例作为块率的代理指标。消息送达健康度代理监控 —— 用 delivered/sent 比率推断块率趋势 import time from collections import defaultdict from dataclasses import dataclass, field from datetime import datetime, timedelta dataclass class MessageTracker: 单条消息的状态追踪 wamid: str sent_at: float status: str sent delivered_at: float | None None target: str dataclass class HealthMonitor: 号码健康度实时监控器 window_seconds: int 3600 # 1 小时窗口 messages: dict[str, MessageTracker] field(default_factorydict) def track_sent(self, wamid: str, target: str): self.messages[wamid] MessageTracker( wamidwamid, sent_attime.time(), targettarget ) def track_delivered(self, wamid: str): if msg : self.messages.get(wamid): msg.status delivered msg.delivered_at time.time() def get_delivery_ratio(self) - float: 返回送达率 —— 用于反向推断块率上限 now time.time() window_msgs { k: v for k, v in self.messages.items() if now - v.sent_at self.window_seconds } if not window_msgs: return 1.0 delivered sum(1 for m in window_msgs.values() if m.status delivered) return delivered / len(window_msgs) def is_at_risk(self) - tuple[bool, str]: 判断当前是否处于风险区间 ratio self.get_delivery_ratio() total sum(1 for m in self.messages.values() if time.time() - m.sent_at self.window_seconds) if total 50: return False, 样本不足 if ratio 0.90: return True, f送达率 {ratio:.1%}疑似块率较高 → 建议暂停营销发送 if ratio 0.95: return True, f送达率 {ratio:.1%}接近 Yellow 边界 → 审查近期模板 return False, f送达率 {ratio:.1%}健康 monitor HealthMonitor() # 在 Webhook 处理中调用 # monitor.track_sent(wamid, target) # monitor.track_delivered(wamid) # at_risk, detail monitor.is_at_risk()这套代码不保证 100% 精确但能让你在 Meta 正式通知你你的号码变红了之前 1-2 天就捕捉到趋势异常争取到反应时间窗口。4. 模板质量你不知道的另一层评分4.1 模板质量评分独立于号码评分一个常被误解的事实模板有自己独立的质量评分它和你号码的评分是两套并行系统。一个 GREEN 号码可能因为某个模板被频繁举报而导致该模板被暂停——但号码本身不受影响。反过来一个 RED 模板如果继续大量发送会把整条号码拖下水。// template_quality_update Webhook 事件 { eventType: template_quality_update, eventDetails: { templateId: 123456789, templateName: promo_flash_sale, templateLanguage: en, meta: { previousQualityScore: GREEN, newQualityScore: RED } } }4.2 模板惩罚级联触发次数后果恢复机制第 1 次 RED模板被 PAUSED 3 小时自动恢复第 2 次 RED模板被 PAUSED 6 小时自动恢复第 3 次 RED模板被永久 DISABLED不可恢复需重新提交新模板所以你的模板质量本质上是一个三次机会的倒计时。处理策略模板质量状态机 —— 防止级联禁用 from enum import Enum class TemplateQuality(Enum): GREEN green YELLOW yellow RED red UNKNOWN unknown class TemplateStateMachine: def __init__(self, template_name: str): self.name template_name self.red_count 0 # RED 累计次数 self.last_red_at None # 最后一次变红的时刻 self.is_disabled False def on_quality_update(self, new_score: TemplateQuality): if new_score TemplateQuality.RED: self.red_count 1 self.last_red_at datetime.now() if self.red_count 3: self.is_disabled True return self._alert( f模板 {self.name} 第 {self.red_count} 次 RED已被永久禁用 ) pause_hours 3 if self.red_count 1 else 6 return self._alert( f模板 {self.name} RED第 {self.red_count}/3 次暂停 {pause_hours}h。 f建议立即停止使用此模板审查内容→重新提交 ) elif new_score TemplateQuality.GREEN: # GREEN 后不会重置计数器但风险降低 return self._alert( f模板 {self.name} 恢复 GREEN历史 RED {self.red_count} 次 ) def _alert(self, msg: str): # 实际生产中接到钉钉/飞书/PagerDuty print(f[TemplateQuality] {msg}) return msg5. 质量 Webhook 工程落地phone_number_quality_update5.1 这是你最不能错过的 Webhook 事件phone_number_quality_update是 Meta 直接告诉你号码评分变了的唯一正式渠道。它不像messages或statuses那样高频但每一条都值得立即响应。// 升级事件 { eventType: phone_number_quality_update, eventDetails: { displayPhoneNumber: 6281234567890, meta: { event: UPGRADE, currentLimit: TIER_10K, oldLimit: TIER_2K } } } // 降级/标记事件 { eventType: phone_number_quality_update, eventDetails: { displayPhoneNumber: 6281234567890, meta: { event: FLAGGED, reason: QUALITY_DECREASE } } }5.2 生产级处理代码phone_number_quality_update 处理管线 —— Do Not Ignore import json from datetime import datetime from dataclasses import dataclass from enum import Enum class QualityEvent(Enum): UPGRADE UPGRADE DOWNGRADE DOWNGRADE FLAGGED FLAGGED UNFLAGGED UNFLAGGED THROUGHPUT_UPGRADE THROUGHPUT_UPGRADE dataclass class QualityUpdate: phone: str event: QualityEvent current_limit: str | None None old_limit: str | None None reason: str | None None timestamp: datetime datetime.now() def handle_phone_number_quality_update(payload: dict): details payload.get(eventDetails, {}) meta details.get(meta, {}) phone details.get(displayPhoneNumber, unknown) event_type meta.get(event, ) update QualityUpdate( phonephone, eventQualityEvent(event_type), current_limitmeta.get(currentLimit), old_limitmeta.get(oldLimit), reasonmeta.get(reason), ) match update.event: case QualityEvent.UPGRADE: # 好事Tier 升级更新系统容量配置 _log_upgrade(update) _update_rate_limiter_capacity(update.current_limit) case QualityEvent.DOWNGRADE: # 坏事Tier 降级立即暂停所有营销计划 _log_downgrade(update) _pause_all_marketing_campaigns(phone) _notify_oncall(f[URGENT] {phone} 降级至 {update.current_limit}) case QualityEvent.FLAGGED: # 糟糕号码被标记停止所有主动发送仅回复客户消息 _log_flagged(update) _disable_all_proactive_sending(phone) _schedule_quality_recovery_plan(phone) _notify_oncall(f[CRITICAL] {phone} FLAGGED, reason{update.reason}) case QualityEvent.UNFLAGGED: # 恢复但不要立即恢复全量发送逐步来 _log_unflagged(update) _gradual_ramp_up(phone, target_percent0.30) def _disable_all_proactive_sending(phone: str): 将所有队列中的该号码主动发送任务标记为 HALTED # 实际操作标记队列、暂停定时任务、设置 Redis 标志位 pass def _schedule_quality_recovery_plan(phone: str): 7 天恢复计划 Day 1-2: 仅回复客户发起的消息Service 类型 Day 3-4: 对活跃客户发送 Utility 模板订单更新、预约提醒 Day 5-6: 小批量 Marketing 模板20% 正常量仅发给高互动用户 Day 7: 评估质量是否恢复为 YELLOW 以上 pass def _update_rate_limiter_capacity(tier: str): 根据 Tier 更新令牌桶容量 tier_capacity { TIER_250: 250, TIER_2K: 2000, TIER_10K: 10000, TIER_100K: 100000, } # 更新全局限流器 pass收到FLAGGED后最关键的一步立即停止所有营销发送。许多团队犯的错误是再等等看是不是误报——没有误报Meta 不会无缘无故标记你。7 天内如果你继续发送、继续收到举报号码将从 FLAGGED → RESTRICTED → 永久封禁这个过程是不可逆的。6. 质量监控仪表盘不看 Meta Manager 也能知道状态Meta Business Manager 的质量评分页面延迟严重有时候半天都不更新而且手动查看不适合多号矩阵。你需要一套自建的质量监控管线。质量监控仪表盘 —— 多号码实时健康度面板 import threading import time from collections import defaultdict from datetime import datetime, timedelta class QualityDashboard: 聚合多号码质量指标提供实时健康度快照 def __init__(self): self._lock threading.Lock() self.phones: dict[str, dict] defaultdict(lambda: { quality_rating: UNKNOWN, current_tier: TIER_250, delivery_rate_1h: 1.0, block_proxy_1h: 0.0, # 送达率反推的块率代理 flagged: False, last_quality_event: None, templates_active: 0, templates_paused: 0, }) def update_quality_from_webhook(self, phone: str, update: QualityUpdate): with self._lock: p self.phones[phone] p[last_quality_event] update.timestamp.isoformat() match update.event: case QualityEvent.UPGRADE: p[current_tier] update.current_limit case QualityEvent.DOWNGRADE: p[current_tier] update.current_limit case QualityEvent.FLAGGED: p[flagged] True p[quality_rating] RED case QualityEvent.UNFLAGGED: p[flagged] False p[quality_rating] YELLOW # 恢复后通常为 YELLOW def get_health_snapshot(self) - list[dict]: 返回所有号码的健康度快照按风险从高到低排序 with self._lock: snapshots [] for phone, data in self.phones.items(): risk_score self._calculate_risk(data) snapshots.append({ phone: phone, **data, risk_score: risk_score, }) return sorted(snapshots, keylambda x: x[risk_score], reverseTrue) def _calculate_risk(self, data: dict) - int: 风险评分 0-100100 为最高风险 score 0 if data[flagged]: score 50 if data[quality_rating] RED: score 30 if data[delivery_rate_1h] 0.90: score 20 if data[templates_paused] 0: score 10 return min(score, 100) dashboard QualityDashboard()配合一段定时采样脚本你可以每分钟输出一次健康状态def periodic_health_check(): while True: snapshots dashboard.get_health_snapshot() for s in snapshots: if s[risk_score] 50: # 高风险号码告警 pass time.sleep(60)7. 评分恢复策略从 RED 走回 GREEN从 RED 恢复到 GREEN 是一个工程约束下的等待游戏。Meta 的金规则很简单7 天内不再收到负面信号。但这不代表你什么都不做。恢复四步法步骤操作为什么时间窗① 立即止损暂停所有 Marketing 模板发送仅保留 Service回复客户消息每多发一条可能被举报的消息都在延长恢复期第 1 分钟内② 排查根因找出是哪个模板、哪批用户导致了举报不改根因恢复后马上再翻车第 1 小时内③ 清洗列表移除近 30 天未互动的用户标记高举报率号码段降低后续发送的负反馈概率第 1 天内④ 渐进恢复第 3 天发 Utility订单提醒等轻度消息第 5 天发 20% 量的 Marketing让 Meta 看到这个号码在变好第 3-7 天渐进恢复发送量 ramp-up 控制器 class GradualRampUp: def __init__(self, normal_daily_limit: int): self.normal_limit normal_daily_limit self.ramp_schedule { 1: 0.00, # Day 1: 0% 2: 0.00, # Day 2: 0% 3: 0.10, # Day 3: 10% 4: 0.10, # Day 4: 10% 5: 0.25, # Day 5: 25% 6: 0.50, # Day 6: 50% 7: 0.75, # Day 7: 75% 8: 1.00, # Day 8: 100% } self.days_since_flagged 0 def get_daily_quota(self) - int: ratio self.ramp_schedule.get(self.days_since_flagged, 1.0) return int(self.normal_limit * ratio) def can_send_marketing(self) - bool: 前 4 天禁止 Marketing 类型消息 return self.days_since_flagged 48. 出海实战从数据清洗到质量闭环说的都是 Meta 侧的规则现在拉回到实际业务线。跨境 B2B 场景里一个最典型的翻车路径是这样的花了一周时间在 WhatsApp 群组里获取了 3000 个潜在客户号码 → 导入群发工具 → 发一轮Hello, Im XXX from ABC company → 两天后质量评分变红 → 号码被封问题出在第三环节拿到号码直接群发 把举报按钮送到不感兴趣的人面前。正确的闭环应该是这样阶段动作工具/手段效果① 精准提取从目标 WhatsApp 群组按国家和活跃度筛选出高意向成员过滤掉已退群/不活跃的号码号码提取与备份工具 WAExport支持多格式导出和按国家代码过滤生成干净的 1500 人高质量列表② 验证清洗批量验证号码是否仍注册 WhatsApp剔除无效号码号码验证过滤器淘汰 300 无效号剩余 1150③ 分层触达按群组活跃度分三层高频互动者先发个性化消息低频者用 Utility 模板试探沉默者暂时不打群发触达工具 WASender人类行为模拟自定义变量间隔延迟互动率 22%举报率 0.3%④ 质量回收监控送达率/互动率/退订率标记负面反馈用户自建质量监控系统前文代码维持 YELLOW 以上稳步升 Tier关键差异在于不是拿到号码就发而是拿到号码后先洗。无效号码被过滤后剩下的都是真实活跃 WhatsApp 用户。再通过间歇发送 人类行为模拟随机延迟、非均匀间隔、变量内容让每次群发看起来像真人对话而非机器广播。这正是 Meta 质量评分系统最愿意奖励的发送模式。9. 生产自检清单上线前逐条确认每一条都对应真实翻车案例[ ] 是否配置了phone_number_quality_updateWebhook 订阅[ ] 是否对 FLAGGED 事件实现了自动暂停营销发送[ ] 是否建立了送达率与块率代理指标的实时监控[ ] 是否对每个模板记录了 RED 累计次数防止第 3 次直接禁用[ ] 发送列表是否经过号码验证清洗是否排除近 30 天未互动用户[ ] 新模板上线前是否先用 50 个高互动用户做小批测试[ ] 是否有 Opt-in 记录可追溯Meta 要求留存至少 30 天[ ] 是否有 7 天渐进恢复计划不靠等而是有节奏地恢复[ ] 多号码矩阵是否共享 Portfolio 级质量评分是否把风险号码隔离[ ] 告警通道是否覆盖非工作时间凌晨 2 点的 FLAGGED 谁来响应10. FAQQ1: 质量评分 GREEN 就一定安全吗不是。GREEN 只代表过去 7 天表现良好今天的一次劣质群发可能在 2 小时内让你变 RED。评分是流式的不是证书。Q2: 可以主动查询当前质量评分吗Meta 没有直接提供 API 查询质量评分字段quality_rating 不会出现在 GET /phone_number 的响应中。你只能通过 WhatsApp Manager 页面查看或者订阅phone_number_quality_updateWebhook 被动接收变更通知。另一个办法是通过获取号码的quality_score字段部分 BSP 提供。Q3: 用户 Block 和举报垃圾信息有什么区别Block 是用户不想再收到你的消息——严重度中等影响评分但不一定触发 FLAGGED。举报垃圾信息Report Spam是用户认为你在发垃圾信息——严重度最高直接触发 Meta 人工或自动化审查最坏情况是立即暂停模板或号码。Q4: 号码被 FLAGGED 后能否申诉不能直接申诉。你只能等 7 天让 Meta 自动重新评估。但你可以做的是停止发送 提高回复率主动回复客户 Service 消息 确保不再收到负面信号。7 天后如果评估改善状态会自动恢复为 CONNECTED。Q5: Portfolio 级评分2025 年 10 月后意味着什么从 2025 年 10 月起消息限额不再按单个号码计算而是按整个 Business Portfolio 共享。坏消息是如果 Portfolio 内有一个号码是 RED它可能拖整个 Portfolio 的后腿。好消息是新号码加入后直接继承 Portfolio 的 Tier不需要从 250 开始爬。Q6: 质量评分能刷回来吗技术上没有刷这个概念。但你可以通过用高质量模板给高互动用户发 Utility 消息来加速恢复——Utility 消息被举报的概率远低于 Marketing。11. 参考来源Meta WhatsApp Cloud API - Quality RatingWhatsApp Business Platform - Messaging LimitsWhatsApp Webhooks Reference - phone_number_quality_updateWhatsApp Quality Score Guide 2025WhatsApp Message Limits and Quality Rating Explained本文所有代码基于 Python 3.11 编写Webhook 处理建议配合异步队列Celery/Redis使用以应对 20s 响应超时限制。