
Fizzy Webhooks API 完全指南事件订阅、投递溯源与签名校验【免费下载链接】fizzyKanban as it should be. Not as it has been.项目地址: https://gitcode.com/GitHub_Trending/fizzy2/fizzyFizzy 的 Webhooks API 允许你在看板Board上发生特定事件时让 Fizzy 主动向你的应用发送 HTTP 回调是实现卡片流转同步、即时通知、机器人自动化等集成的核心通道。本文以 docs/api/sections/webhooks.md 为骨架结合仓库中 WebhooksController、Webhook 模型、Webhook::Delivery 模型 等源码实现完整讲解 Webhook 的增删改查、激活、投递历史查询、签名校验原理与安全防护细节读完即可对接 Fizzy 的事件流。前置认知权限与认证Webhook 的权限模型在文档中写得很明确只有账户管理员account admins可以列出、查看、创建、更新、删除或重新激活 Webhook也只有账户管理员可以访问投递历史delivery history。这一约束在源码层面由控制器中的before_action :ensure_admin强制保证同时每个 Webhook 都强关联于某个 Boardbelongs_to :board所有端点都必须在/boards/:board_id之下按板隔离访问。调用这些端点前需要先完成 API 认证Fizzy 支持两种方式详见 docs/api/README.mdPersonal access tokens长期有效的令牌适合脚本与集成请求时以Authorization: Bearer token头携带Magic link authentication基于会话的认证适合原生应用。所有列表类接口均使用动态分页初始页返回的结果条数少于后续页若还有更多数据响应会携带Link头relnext指向下一页。列表参数如标签通过重复参数名并追加[]传递。下文所有示例中的基础地址为http://app.fizzy.localhost:3006实际使用请替换为你的 Fizzy 部署域名。管理 Webhook五个核心 CRUD 端点Webhook 的完整生命周期管理由webhooks资源提供路由定义见 config/routes.rbresources :webhooks内嵌activation子资源。创建与更新时使用wrap_parameters :webhook参数包装因此请求体统一为{ webhook: { ... } }形式。列出看板的所有 WebhookGET /:account_slug/boards/:board_id/webhooks返回该看板下已分页的 Webhook 列表。控制器中通过board.webhooks.ordered取出数据排序规则为名称升序、id 降序见 webhook.rb 的scope :ordered。单个元素结构如下[ { id: 03f5v9zkft4hj9qq0lsn9ohcm, name: Production API, payload_url: https://api.example.com/webhooks, active: true, signing_secret: p94Bx2HjempCdYB4DTyZkY1b, subscribed_actions: [card_published, card_assigned, card_closed], created_at: 2025-12-05T19:36:35.534Z, url: http://app.fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcy/webhooks/03f5v9zkft4hj9qq0lsn9ohcm, board: { id: 03f5v9zkft4hj9qq0lsn9ohcy, name: Fizzy, all_access: true, created_at: 2025-12-05T19:36:35.534Z, url: http://app.fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcy, creator: { id: 03f5v9zjw7pz8717a4no1h8a7, name: David Heinemeier Hansson, role: owner, active: true, email_address: davidexample.com, created_at: 2025-12-05T19:36:35.401Z, url: http://app.fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7 } } } ]该 JSON 结构由 app/views/webhooks/_webhook.json.jbuilder 生成注意payload_url字段直接映射 Webhook 的url属性signing_secret会随响应返回请妥善保管后面签名校验要用board嵌套对象复用boards/board模板包含creator信息。响应同时带有ETag与Cache-Control头可配合If-None-Match做条件请求以节省带宽。查看单个 WebhookGET /:account_slug/boards/:board_id/webhooks/:id返回与列表项完全相同的 Webhook 结构show视图复用同一 partial。内部通过board.webhooks.find(params[:id])在板范围内精确查找确保跨板越权访问不会命中。创建 WebhookPOST /:account_slug/boards/:board_id/webhooks请求体必须用webhook包裹{ webhook: { name: Production API, url: https://api.example.com/webhooks, subscribed_actions: [card_published, card_assigned, card_closed] } }subscribed_actions决定该 Webhook 订阅哪些事件动作接受以下全部取值与 webhook.rb 中PERMITTED_ACTIONS白名单一一对应动作触发场景card_assigned卡片被指派负责人card_unassigned卡片被取消指派card_closed卡片被关闭card_reopened卡片被重新打开card_postponed卡片被推迟card_auto_postponed卡片被系统自动推迟card_board_changed卡片被移动到其他看板card_published卡片被发布card_sent_back_to_triage卡片被退回待分类card_triaged卡片完成分类comment_created卡片产生新评论注意subscribed_actions在入库前会经过normalizes规整——将值去重、转字符串并与白名单求交集白名单之外的动作会被静默过滤因此请严格使用上表枚举。name为必填项缺失时返回422 Unprocessable Entity及错误详情url会被strip去除首尾空白且只允许http/https两种 schemePERMITTED_SCHEMES其他协议或非法 URL 都会触发校验错误。创建成功的响应HTTP/1.1 201 Created Location: http://app.fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcy/webhooks/03f5v9zkft4hj9qq0lsn9ohcm.jsonLocation头指向新资源的 JSON 地址响应体即为完整 Webhook 对象控制器使用render :show, status: :created。成功创建的同时系统会自动生成一个delinquency_tracker关联记录after_create :create_delinquency_tracker!用于后续统计投递状况。更新 WebhookPATCH /:account_slug/boards/:board_id/webhooks/:id请求体{ webhook: { name: Production API, subscribed_actions: [card_closed] } }url创建后不可修改文档明确指出 url 是 immutable不可变的源码中控制器执行webhook.update(webhook_params.except(:url))即在更新时显式剔除url参数——即使你在请求体里传入url也会被忽略。因此变更接收地址只能先删除再重建。更新成功后返回更新后的 Webhook 对象。删除 WebhookDELETE /:account_slug/boards/:board_id/webhooks/:id删除成功后返回204 No Content控制器head :no_content。Webhook 被删除时其关联的投递记录has_many :deliveries, dependent: :delete_all与投递统计器delinquency_tracker会一并清理。重新激活被停用的 WebhookPOST /:account_slug/boards/:board_id/webhooks/:id/activationWebhook 会被自动停用deactivated——典型场景是连续投递失败触发保护机制。文档说明只有管理员可以重新激活该端点在 config/routes.rb 中定义为resource :activation, only: :create由 Webhooks::ActivationsController 处理调用模型的activate方法update! active: true见 webhook.rb成功后返回HTTP/1.1 201 Created响应体为重新激活后的 Webhook 对象。与之对应的停用方法deactivateupdate! active: false同样定义在模型上用于内部投递保护逻辑。active字段用于标记状态模型上还提供了scope :active便于筛选。查询投递历史溯源每一次回调GET /:account_slug/boards/:board_id/webhooks/:webhook_id/deliveries返回某个 Webhook 的投递delivery分页列表仅账户管理员可访问before_action :ensure_admin。每条投递记录由 Webhook::Delivery 承载按created_at与id倒序排列并预加载事件及事件相关卡片信息。响应示例[ { id: 03f5v9zkft4hj9qq0lsn9ohdn, state: completed, created_at: 2025-12-05T19:36:35.534Z, updated_at: 2025-12-05T19:36:36.102Z, request: { headers: { User-Agent: fizzy/1.0.0 Webhook, Content-Type: application/json, X-Webhook-Timestamp: 2025-12-05T19:36:35.401Z } }, response: { code: 200, error: null }, event: { id: 03f5v9zkft4hj9qq0lsn9ohde, action: card_closed, created_at: 2025-12-05T19:36:35.401Z, creator: { id: 03f5v9zjw7pz8717a4no1h8a7, name: David Heinemeier Hansson }, eventable: { type: Card, id: 03f5v9zkft4hj9qq0lsn9ohdb, url: http://app.fizzy.localhost:3006/897362094/cards/1 } } } ]投递状态机与字段语义state投递生命周期状态取值为pending已创建未开始、in_progress请求进行中、completed请求完成、errored发送过程抛错见 delivery.rb 的枚举定义request/response对于尚未开始或仍在进行中的投递二者可为nullresponse.error失败原因标识从源码的异常捕获分支可归纳出以下取值——private_uri目标解析到内网/私有地址、response_too_large响应体超过 100KB、dns_lookup_failedDNS 解析失败、connection_timeout连接或读取超时阈值 7 秒、destination_unreachable连接被拒绝/主机不可达/连接重置、failed_tlsTLS 握手失败event触发本次投递的原始事件包含动作类型、创建者与事件目标对象如某张卡片可据此追溯是哪一次操作引发了回调。request与response的 JSON 序列化由 app/views/webhooks/deliveries/_delivery.json.jbuilder 完成request调用delivery.sanitized_requestresponse调用delivery.response_summary。特别注意文档末尾的说明——投递请求头中会省略X-Webhook-Signature这是刻意的安全设计sanitized_request会except(X-Webhook-Signature)避免签名密钥相关数据被回显泄露你在校验签名时需要自行重新计算。成功判定标准判断一次投递是否成功可依据response_summaryerrored? || completed?且code处于200..299且无错误标识时succeeded?为真见 delivery.rb。也就是说只要目标返回 2xx 就算成功无论事件处理结果如何。投递机制与签名校验原理理解投递侧实现能帮助你正确消费事件并验证来源真实性。核心逻辑在 Webhook::Delivery 中触发链路事件产生时Webhook::Triggerable 提供triggered_by(event)作用域匹配event.board且subscribed_actions包含event.action的 Webhook随后对每个命中的 Webhook 调用trigger(event)创建一条delivery记录——账户已取消account.cancelled?时不会触发投递。投递记录创建后通过after_create_commit :deliver_later进入后台任务异步发送因此列表里刚出现的投递可能仍处于pending/in_progress状态。请求头与签名每次投递会携带以下请求头请求头值用途User-Agentfizzy/1.0.0 Webhook标识发送方Content-Type视目标而定见下文内容协商X-Webhook-SignatureHMAC-SHA256 十六进制摘要验签X-Webhook-Timestamp事件创建时间的 ISO8601 UTC防重放参考其中签名计算方式delivery.rbsignature OpenSSL::HMAC.hexdigest(SHA256, webhook.signing_secret, payload)即以 Webhook 的signing_secret创建时通过has_secure_token自动生成的随机密钥见 webhook.rb为密钥对请求体原始字节做 HMAC-SHA256输出十六进制字符串。接收方可以用同样的算法自行复算比对验签示例Rubyrequire openssl require json body request.body.read # 请求体原始字节 secret p94Bx2HjempCdYB4DTyZkY1b # 创建 webhook 时返回的 signing_secret expected OpenSSL::HMAC.hexdigest(SHA256, secret, body) signature request.headers[X-Webhook-Signature] if ActiveSupport::SecurityUtils.secure_compare(expected, signature) # 签名合法处理事件 else # 拒绝该请求 end配合X-Webhook-Timestamp还可以做时间窗校验抵御重放攻击。注意投递历史 API 中展示的请求头故意省略了该签名头验签所需的签名只能在你的接收端主动计算。事件载荷与目标适配载荷模板为webhooks/eventJSON 与 HTML 两种格式见 app/views/webhooks/event.json.jbuilder 与 app/views/webhooks/event.html.erb。Fizzy 还针对常见第三方做了专门适配delivery.rbSlackContent-Type: application/json载荷为{ text: ... url|Open in Fizzy }格式便于直接作为 Slack Incoming Webhook 消息CampfireContent-Type: text/html发送 HTML 渲染的事件内容BasecampContent-Type: application/x-www-form-urlencoded将 HTML 内容序列化为表单字段其他目标默认application/json发送事件对象的 JSON 表示。这些能力依赖 webhook.rb 中的 URL 正则识别//hooks.slack.com/services/T.../B...Slack、/rooms/id/token/messagesCampfire、/acct/integrations/.../buckets/.../chats/.../linesBasecamp。如果你使用这些平台的 Webhook 地址无需额外配置即可获得对应格式。安全与可靠性硬约束从 delivery.rb 的常量与逻辑可归纳出接收端必须满足的条件防 SSRF发送前通过Surfguard.resolve_public_ips(uri.host)解析目标域名仅允许解析到公网 IP解析到私有/内网地址时直接记录private_uri错误不发起请求同时用解析出的 IP 直连http.ipaddr resolved_ip防止 DNS 重绑定攻击超时控制连接与读取超时均为ENDPOINT_TIMEOUT 7.seconds超时记录为connection_timeout响应体积上限MAX_RESPONSE_SIZE 100.kilobytes流式读取超过即中断并标记response_too_large投递重试与清理投递状态机支持失败后的重新发送cleanup方法会批量删除超过STALE_TRESHOLD 7.days的陈旧记录默认每批 500 条投递历史不会无限膨胀。源码地图按图索骥想深入了解实现细节可以按以下路径深入源码控制器app/controllers/webhooks_controller.rbCRUD 与参数处理、app/controllers/webhooks/deliveries_controller.rb投递历史、app/controllers/webhooks/activations_controller.rb重新激活模型app/models/webhook.rb动作白名单、URL 校验、激活/停用、专用集成识别、app/models/webhook/triggerable.rb事件触发匹配、app/models/webhook/delivery.rb投递执行、签名、错误分类、app/models/webhook/delinquency_tracker.rb投递统计序列化视图app/views/webhooks/_webhook.json.jbuilder、app/views/webhooks/deliveries/_delivery.json.jbuilder、app/views/webhooks/deliveries/_event.json.jbuilder路由config/routes.rb后台任务app/jobs/webhook 目录下的投递 Job。常见错误速查结合 docs/api/README.md 的错误模型与本文端点特性整理如下状态码场景400 Bad Request请求格式错误或缺少必需参数401 Unauthorized认证失败或访问令牌无效403 Forbidden非账户管理员访问 Webhook 端点404 Not Found资源不存在或无访问权限含跨板越权422 Unprocessable Entity校验失败如name为空、url协议非 http/https、subscribed_actions全为非法值等响应体会附字段级错误详情500 Internal Server Error服务端意外错误最后提醒两点实战要点一是subscribed_actions只接受白名单枚举非法值会被静默过滤务必按上表精确填写二是接收端应实现 HMAC-SHA256 验签密钥为创建 Webhook 时返回的signing_secret并建议结合X-Webhook-Timestamp做时间窗校验防止伪造回调。【免费下载链接】fizzyKanban as it should be. Not as it has been.项目地址: https://gitcode.com/GitHub_Trending/fizzy2/fizzy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考