Skip to main content

通知渠道

用户可注册多个投递渠道(webhook、Telegram、Slack、Discord、电子邮件),并将告警规则绑定到这些渠道上。 摘要与简报通知使用 新闻摘要与简报方法论 中记录的同一故事池和编辑护栏。

GET /api/notification-channels

列出调用方已注册的渠道和告警规则。

POST /api/notification-channels

基于 action 分发的写入端点。请求体中的 action 字段决定执行哪一类变更: 所有 action 均要求 Clerk bearer + PRO,且此处的 PRO 特指已计费的权益记录(tier >= 1)。与网关的 tier-1 REST 权限检查不同,仅有 pro 角色但没有权益记录的 Clerk 会话不满足要求:通知投递在 Convex 内部还会被再次校验(assertProEntitlement),因此边缘层直接返回清晰的 403 pro_required,而不是让请求在更深层以更无用的错误失败。无效的 action 返回 400 Unknown action。请求通过 RELAY_SHARED_SECRET 转发至 Convex。 仅当权益被确认为非 Pro 时,没有已计费权益记录的调用方才会收到 403 pro_required。当权益校验本身处于不确定状态时,本端点的权限检查改为遵循共享的计费校验契约:对 entitlement_verification_unavailable / renewal_verification_pending / renewal_verification_failed 返回带 Retry-AfterX-Billing-Verification503;对已确认失效的订阅返回 403 subscription_lapsed。参见错误处理 客户端在向用户暴露失败之前,应至少按 Retry-After 重试一次该 503 —— 仪表盘自身的服务层(src/services/notification-channels.ts)会执行且仅执行一次有界重试。对 entitlement_verification_unavailable 而言,早于 Retry-After 的重试是无意义的:该结果在服务端会被短时负缓存,提前重试只会拿到同一个结果。另外两个 renewal_verification_* 代码不会被负缓存,但其延迟对应真实的计费提供方复核或冷却期,因此提前重试同样只会得到相同状态。
  • 幂等性: POST /api/notification-channels 支持可选的 Idempotency-Key。使用相同 key 和相同请求体重试时,会重放原始响应,而不是再次应用渠道动作。

Webhook 投递契约

当告警触发时,已注册的 webhook URL 将收到:
  • Method: POST
  • Headers:
    • Content-Type: application/json
    • X-WM-Signature: sha256=<HMAC-SHA256(body, channelSecret)>
    • X-WM-Delivery-Id: <ulid>
    • X-WM-Event: <event-name>
  • Body(信封 v1):
签名校验:hmac_sha256(rawBody, channelSecret) == X-WM-Signature[7:]
信封版本在两个生产者之间共享(notification-relayseed-digest-notifications)。升级版本需协同更新。

POST /api/notify

面向 PRO 调用方的认证事件发布端点。要求 Clerk bearer 认证和有效的 PRO 权益,随后将接受的事件入队到通知队列。中继内部控制事件(如 flush_quiet_heldchannel_welcome)为保留事件,会被拒绝。 错误码:401(缺少/无效 JWT)、403 pro_required;与本文档中所有 Pro 门控端点一样,当权益校验本身处于不确定状态时遵循共享的计费校验契约:对 entitlement_verification_unavailable / renewal_verification_pending / renewal_verification_failed 返回带 Retry-AfterX-Billing-Verification503;对已确认失效的订阅返回 403 subscription_lapsed。参见错误处理
  • 幂等性: 支持可选的 Idempotency-Key。使用相同 key 和相同请求体重试时,会重放原始入队响应,而不是再次发布通知。

Telegram

GET /api/telegram-feed

主题标签页式 Telegram Intel 面板的第一方浏览器路径。接受 limittopicchannel 参数;不存在按用户(userId)的调用形式。需要仪表盘会话凭证(wms_),响应为 private, max-age=30 — 不可被公开缓存,无凭证的请求返回 401 并附带 no-store。消息正文为 R4。 程序化访问与合作方访问请改用已认证的 RPC GET /api/intelligence/v1/list-telegram-feed

YouTube

GET /api/youtube/embed?videoId=...

带 CSP 兼容封装的 SSR YouTube embed iframe。用于绕过桌面应用中的 WKWebView 自动播放限制。

GET /api/youtube/live?channel=<handle>?videoId=<11-char-id>

返回 YouTube 频道(channel — 带/不带 @ 前缀的 handle)或指定视频(videoId — 11 字符 YouTube id)的直播元数据。两个参数至少需提供其一;否则返回 400 Missing channel or videoId parameter。频道查询响应缓存 10 分钟,videoId 查询缓存 1 小时。 首先通过 Railway 中继代理(用于 YouTube 抓取的住宅代理)。中继失败时,回退到 YouTube oEmbed(用于 videoId)或直接频道抓取 — 两者从数据中心 IP 均不可靠。

Slack 集成

POST /api/slack/oauth/start

需认证(Clerk JWT + PRO)。请求体为空。服务端生成一次性 CSRF state token,以该 state 为键将调用方的 userId 存入 Upstash(10 分钟 TTL),并返回 Slack 授权 URL,供前端在弹窗中打开。
错误码:401(缺少/无效 JWT)、403 pro_required、503(OAuth 未配置或 Upstash 不可用)。此处的 503 也可能是可重试的计费校验拒绝 —— entitlement_verification_unavailable / renewal_verification_pending / renewal_verification_failed,并携带 Retry-AfterX-Billing-Verification;已确认失效的订阅为 403 subscription_lapsed。由于配置错误导致的 503 不可重试,请依据 code 字段而非仅凭状态码分支。参见错误处理

GET /api/slack/oauth/callback

无需认证 — Slack 重定向后弹窗会跳转到此。校验 state token,用 code 换取 incoming-webhook URL,使用 AES-256-GCM 加密该 webhook 并存入 Convex。返回一段极简 HTML 页面,通过 postMessage 通知 opener 并关闭。

Discord 集成

POST /api/discord/oauth/start

需认证(Clerk JWT + PRO)。与 Slack 的 start 路由形态一致 — 返回 { oauthUrl } 供弹窗使用,错误码集合也相同,包括上文描述的计费校验 503/403 代码。

GET /api/discord/oauth/callback

无需认证。用 code 换取信息,存储 guild webhook,并通过 postMessage 通知 opener。