32 KiB
M8 通知与Webhook — Chatwoot 功能梳理文档
参照仓库:chatwoot-reference
产出日期:2026-05-22
版本基准:Chatwoot v3.x
目录
- 通知模型与类型
- 通知设置(NotificationSetting)
- 通知订阅(NotificationSubscription / Push订阅)
- 通知生成与投递流程
- NotificationListener — 事件驱动的通知创建
- ActionCable实时推送
- Push通知(浏览器Push + FCM)
- Email通知
- 通知Snooze与去重
- Webhook(Account Webhook)
- IntegrationHook(集成Hook)
- WebhookListener — 事件驱动的Webhook投递
- HookListener — 集成Hook执行
- InstallationWebhook(平台级事件推送)
- 事件分发体系(Dispatcher架构)
1. 通知模型与类型
功能描述
Notification 是 Chatwoot 应用内通知的核心数据模型,记录针对特定用户的通知条目。每个通知绑定到一个 primary_actor(目前仅 Conversation)和一个可选的 secondary_actor(如 Message 或 User),通过 polymorphic 关联实现。通知类型通过 enum 定义,涵盖对话创建、分配、提及、新消息、SLA违规等场景。
用户操作流程
- 坐席登录后在侧边通知面板看到未读通知列表
- 点击某条通知可标记为已读,跳转至对应对话
- 可批量标记全部已读、删除单条通知、删除全部/已读通知
- 可对通知进行" snooze "(延迟提醒),到期后自动恢复为未读
- 通知计数(unread_count + count)实时更新
涉及的API端点
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/accounts/{account_id}/notifications |
获取通知列表(支持分页、过滤 read/snoozed) |
| PATCH | /api/v1/accounts/{account_id}/notifications/{id} |
标记单条通知已读 |
| POST | /api/v1/accounts/{account_id}/notifications/{id}/unread |
标记通知为未读 |
| POST | /api/v1/accounts/{account_id}/notifications/{id}/snooze |
snooze 通知 |
| DELETE | /api/v1/accounts/{account_id}/notifications/{id} |
删除单条通知 |
| POST | /api/v1/accounts/{account_id}/notifications/read_all |
全部标记已读(可按 primary_actor 过滤) |
| POST | /api/v1/accounts/{account_id}/notifications/destroy_all |
删除全部/已读通知 |
| GET | /api/v1/accounts/{account_id}/notifications/unread_count |
获取未读通知数 |
涉及的数据模型 + 关键字段
- Notification(
notifications表):id(bigint, PK)notification_type(integer, not null) — enum 见下表primary_actor_type/primary_actor_id(string + bigint, polymorphic, not null)secondary_actor_type/secondary_actor_id(string + bigint, polymorphic, optional)user_id(bigint, not null, FK) — 通知目标用户account_id(bigint, not null, FK)read_at(datetime) — 已读时间snoozed_until(datetime) — snooze到期时间last_activity_at(datetime) — 最后活动时间meta(jsonb) — 附加元数据created_at,updated_at
- NOTIFICATION_TYPES enum:
| 类型 | 值 | 说明 |
|---|---|---|
| conversation_creation | 1 | 新对话创建 |
| conversation_assignment | 2 | 对话被分配给坐席 |
| assigned_conversation_new_message | 3 | 被分配的对话收到新消息 |
| conversation_mention | 4 | 对话中被 @提及 |
| participating_conversation_new_message | 5 | 参与中的对话收到新消息 |
| sla_missed_first_response | 6 | SLA首次响应违规 |
| sla_missed_next_response | 7 | SLA后续响应违规 |
| sla_missed_resolution | 8 | SLA解决时间违规 |
- PRIMARY_ACTORS 目前仅允许
Conversation - 性能索引:
idx_notifications_performance (user_id, account_id, snoozed_until, read_at) - 唯一索引:
uniq_primary_actor_per_account_notifications,uniq_secondary_actor_per_account_notifications
涉及的业务逻辑
- NotificationBuilder(
app/builders/notification_builder.rb):- 构建通知前检查:
user_subscribed_to_notification?、blocked contact 过滤、user_can_access_conversation?(权限验证) - secondary_actor 默认为 Current.user
- conversation_creation 类型需订阅确认才创建
- 构建通知前检查:
- NotificationFinder(
app/finders/notification_finder.rb):- 分页查询:RESULTS_PER_PAGE = 15
- 支持过滤:snoozed / read 两种状态(通过
includes参数) - 提供
unread_count和count - 默认按
last_activity_at降序排列
- Notification::MarkConversationReadService:批量将某对话的未读通知标记为已读
- Notification::DeleteNotificationJob:异步批量删除全部或已读通知(type: :all / :read)
涉及的自动化/规则/事件
before_create :set_last_activity_at— 创建时设置活动时间after_create_commit :process_notification_delivery, :dispatch_create_event— 创建后触发 Push/Email 投递 + ActionCable 事件after_update_commit :dispatch_update_event— 更新后触发 ActionCable 事件after_destroy_commit :dispatch_destroy_event— 删除后触发 ActionCable 事件(传序列化数据避免反序列化错误)Notification::RemoveDuplicateNotificationJob— 创建后去重(保留最新,删除同一 user+primary_actor 的旧通知)
2. 通知设置(NotificationSetting)
功能描述
NotificationSetting 是每个用户在每个账户下的通知偏好配置,使用 FlagShihTzu 位运算存储 email 和 push 两种投递渠道的开关。每种通知类型(conversation_creation, conversation_assignment 等)都可以独立开启/关闭 email 和 push 投递。
用户操作流程
- 坐席进入 Profile → Notification Preferences
- 对每种通知类型分别勾选是否接收 Email 通知和 Push 通知
- 保存后即时生效
涉及的API端点
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/accounts/{account_id}/notification_settings |
获取当前用户在该账户的通知设置 |
| PATCH | /api/v1/accounts/{account_id}/notification_settings |
更新通知设置 |
- 请求参数:
{ notification_settings: { selected_email_flags: [...], selected_push_flags: [...] } }
涉及的数据模型 + 关键字段
- NotificationSetting(
notification_settings表):id(bigint, PK)email_flags(integer, default 0) — 位运算存储每种通知类型的 email 开关push_flags(integer, default 0) — 位运算存储每种通知类型的 push 开关account_id(integer, FK)user_id(integer, FK)created_at,updated_at
- 唯一索引:
by_account_user (account_id, user_id) - EMAIL_NOTIFICATION_FLAGS:从 Notification::NOTIFICATION_TYPES 派生,格式
email_conversation_creation,email_conversation_assignment等 - PUSH_NOTIFICATION_FLAGS:同理,格式
push_conversation_creation等 - 位运算查询模式:
flag_query_mode: :bit_operator
涉及的业务逻辑
- NotificationSettingsController#update:
- 通过
selected_email_flags和selected_push_flags数组参数更新位标志 - 使用 FlagShihTzu 的
selected_*_flags=方法批量设置
- 通过
3. 通知订阅(NotificationSubscription / Push订阅)
功能描述
NotificationSubscription 记录用户订阅 Push 通知的设备/浏览器信息,支持 browser_push(Web Push API)和 fcm(Firebase Cloud Messaging)两种类型。每个订阅有唯一 identifier(浏览器 endpoint 或 FCM device_id),用于防止重复订阅和跨账户迁移。
用户操作流程
- 坐席在浏览器中授权 Push 通知权限
- 前端生成 Push subscription 对象(含 endpoint, p256dh, auth)
- 调用 API 创建 NotificationSubscription
- 如果同一浏览器已登录多个账户,系统自动将订阅迁移到当前用户
涉及的API端点
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/notification_subscriptions |
创建/更新 Push 订阅 |
| DELETE | /api/v1/notification_subscriptions |
删除 Push 订阅(按 push_token) |
- 创建请求:
{ notification_subscription: { subscription_type: 'browser_push'/'fcm', subscription_attributes: { endpoint, p256dh, auth } / { device_id, push_token } } }
涉及的数据模型 + 关键字段
- NotificationSubscription(
notification_subscriptions表):id(bigint, PK)subscription_type(integer, not null) — enum: browser_push(1), fcm(2)identifier(text) — 唯一标识(browser endpoint 或 FCM device_id)subscription_attributes(jsonb, not null) — 包含 endpoint, keys(p256dh, auth) 或 device_id, push_token 等user_id(bigint, not null, FK)created_at,updated_at
- 唯一索引:
index_notification_subscriptions_on_identifier
涉及的业务逻辑
- NotificationSubscriptionBuilder:
- 根据 subscription_type 从 subscription_attributes 中提取 identifier
- 如果 identifier 已存在且属于不同用户,自动迁移(
move_subscription_to_user) - 如果 identifier 已存在且属于同一用户,更新订阅信息
- 否则新建订阅
4. 通知生成与投递流程
功能描述
完整的通知生命周期:事件触发 → NotificationListener 捕获 → NotificationBuilder 创建 Notification → after_create_commit 触发投递 → Push/Email 双通道发送 → ActionCable 实时推送前端更新。
通知投递流程图
事件(如 conversation.created)
→ Dispatcher.dispatch(event_name, timestamp, data)
→ SyncDispatcher → ActionCableListener(实时推送)
→ AsyncDispatcher → EventDispatcherJob → NotificationListener
→ NotificationBuilder.perform → 创建 Notification
→ after_create_commit:
1. process_notification_delivery:
- PushNotificationJob → PushNotificationService → browser_push / fcm / ChatwootHub
- EmailNotificationJob → EmailNotificationService → ConversationNotificationsMailer
- RemoveDuplicateNotificationJob → 去重
2. dispatch_create_event → SyncDispatcher → ActionCableListener.notification_created
投递条件检查
- Push 投递条件:用户订阅了 push_{notification_type}? → 检查 notification_setting.push_flags
- Email 投递条件:
- 用户订阅了 email_{notification_type}? → 检查 notification_setting.email_flags
- notification.read_at 为 nil(尚未已读则发邮件)
- user.confirmed_at 不为 nil(邮箱已确认)
- account.within_email_rate_limit?(不超过每日发送限额)
5. NotificationListener — 事件驱动的通知创建
功能描述
NotificationListener 是异步事件监听器,订阅 AsyncDispatcher 中的事件,根据事件类型调用 NotificationBuilder 创建通知。监听以下事件:
监听事件列表
| 事件方法 | 事件名 | 通知逻辑 |
|---|---|---|
| conversation_created | conversation.created | 对 inbox 所有成员创建 conversation_creation 通知 |
| conversation_bot_handoff | conversation.bot_handoff | bot转人工时同上逻辑 |
| assignee_changed | assignee.changed | 为 assignee 创建 conversation_assignment 通知(需 notifiable_assignee_change 且 assignee 不为空) |
| message_created | message.created | 调用 MentionService + NewMessageNotificationService |
消息创建事件的子逻辑
- Messages::MentionService:
- 仅处理 private 消息且包含 @提及 的内容
- 解析
mention://user/{id}和mention://team/{id}格式 - 团队提及会展开为所有团队成员的 user_ids
- 过滤不在 inbox 成员/管理员中的提及用户
- 为每个被提及用户创建 conversation_mention 通知
- 同时将提及用户添加为对话参与者
- Messages::NewMessageNotificationService:
- 仅处理
message.notifiable?的消息 - 为对话 assignee 创建 assigned_conversation_new_message 通知(排除 sender 本身)
- 为对话参与者创建 participating_conversation_new_message 通知
- 去重检查:already_notified? 防止同一条消息重复通知同一用户
- 仅处理
6. ActionCable实时推送
功能描述
ActionCable 是 Chatwoot 实时通信的核心机制,通过 WebSocket 将各类事件推送至前端。SyncDispatcher 中的 ActionCableListener 在事件发生时同步广播,通过 ActionCableBroadcastJob 异步执行实际广播。
用户操作流程
- 前端通过 WebSocket 连接到 RoomChannel(参数:pubsub_token, user_id)
- 连接成功后,stream_from 用户的 pubsub_token 和账户级 "account_{id}" 频道
- 前端 BaseActionCableConnector.js 监听各类事件并更新 Vuex/Pinia store
ActionCableListener 监听事件
| 事件方法 | 事件名 | 广播目标 | 推送数据 |
|---|---|---|---|
| notification_created | notification.created | 通知所属用户的 pubsub_token | notification.push_event_data + unread_count + count |
| notification_updated | notification.updated | 同上 | 同上 |
| notification_deleted | notification.deleted | 同上 | {id} + unread_count + count |
| account_cache_invalidated | account.cache_invalidated | 账户所有 agents 的 tokens | cache_keys |
| message_created | message.created | inbox members + admins + contact_inbox | message.push_event_data |
| message_updated | message.updated | 同上 | 同上 |
| conversation_created | conversation.created | inbox members + admins | conversation.push_event_data |
| conversation_updated | conversation.updated | 同上 | 同上 |
| assignee_changed | assignee.changed | inbox members | conversation.push_event_data |
| team_changed | team.changed | 同上 | 同上 |
| conversation_contact_changed | conversation.contact_changed | 同上 | 同上 |
| conversation_typing_on | conversation.typing_on | inbox members + contact_inbox (排除当前 typing 用户) | conversation + user push_event_data |
| conversation_typing_off | conversation.typing_off | 同上 | 同上 |
| contact_created/updated/deleted | contact.* | account_{id} 频道 | contact push_event_data |
| conversation_mentioned | conversation.mentioned | 被提及用户的 token | conversation push_event_data |
广播机制
- broadcast(account, tokens, event_name, data) 方法:
- tokens 为 pubsub_token 数组(包含用户 token 和账户级 token)
- 通过
ActionCableBroadcastJob.perform_later异步广播 - 对 CONVERSATION_UPDATE_EVENTS 类事件,会重新查询最新数据避免前端乱序
- RoomChannel:
- 连接验证:通过 pubsub_token + user_id 找到合法用户
- stream_from 用户 pubsub_token + "account_{account_id}" 频道
- 订阅时更新 OnlineStatusTracker(在线状态追踪)
- Pubsubable concern:User 模型包含,自动生成 pubsub_token,密码修改时轮换 token
7. Push通知(浏览器Push + FCM)
功能描述
Push 通知是通知到达用户设备的通道,支持三种方式:浏览器 Web Push(VAPID)、Firebase Cloud Messaging(FCM)和 ChatwootHub 云端推送。PushNotificationService 根据用户的通知订阅逐一尝试推送。
推送渠道
| 渠道 | 条件 | 实现方式 |
|---|---|---|
| Browser Push | VapidService.public_key 存在 && subscription.browser_push? | WebPush.payload_send(VAPID签名) |
| FCM Push | FCM_PROJECT_ID/KEY 配置 && subscription.fcm? | Notification::FcmService → FCM.new → push |
| ChatwootHub | ChatwootApp.chatwoot_cloud? | ChatwootHub.push_notification_url → HTTP POST |
涉及的业务逻辑
- Notification::PushNotificationService:
- 先检查
user_subscribed_to_notification?(push_flags 位检查) - 遍历
user.notification_subscriptions,对每个订阅尝试 browser_push → fcm → chatwoot_hub - push_message 包含:title, tag(防重复), url(跳转到对话页面)
- 先检查
- VapidService:管理 VAPID 密钥(从 GlobalConfig/InstallationConfig 读取或自动生成)
- Notification::FcmService:Google Auth ServiceAccountCredentials → 生成 access_token → FCM client push
- Notification::PushTestService:推送测试功能,返回 success/failure/skipped 状态用于前端展示
涉及的配置
VAPID_KEYS(InstallationConfig)— Web Push VAPID 密钥对FCM_PROJECT_ID,FCM_SERVICE_ACCOUNT_KEY(EnvironmentConfig)— FCM 配置CHATWOOT_CLOUD(InstallationConfig)— 是否启用 ChatwootHub 推送
8. Email通知
功能描述
Email 通知通过 AgentNotifications::ConversationNotificationsMailer 发送,每种通知类型对应一个邮件方法,邮件内容使用 Liquid 模板渲染,支持自定义邮件模板。
邮件类型对应表
| 通知类型 | Mailer方法 | 邮件主题 |
|---|---|---|
| conversation_creation | conversation_creation(conversation, agent, _user) | "{agent_name}, A new conversation [ID-{display_id}] has been created in {inbox_name}" |
| conversation_assignment | conversation_assignment(conversation, agent, _user) | "{agent_name}, A new conversation [ID-{display_id}] has been assigned to you." |
| conversation_mention | conversation_mention(conversation, agent, message) | "{agent_name}, You have been mentioned in conversation [ID-{display_id}]" |
| assigned_conversation_new_message | assigned_conversation_new_message(conversation, agent, message) | "{agent_name}, A new message in conversation [ID-{display_id}]" |
| participating_conversation_new_message | participating_conversation_new_message(conversation, agent, message) | 同上 |
| sla_missed_* | sla_missed_first_response / next / resolution | SLA违规通知 |
投递条件
- Notification::EmailNotificationService:
notification.read_at为 nil — 用户尚未通过 Push 已读(避免重复通知)notification.user.confirmed_at不为 nil — 邮箱已验证user_subscribed_to_notification?— email_flags 位检查notification.account.within_email_rate_limit?— 邮件日限额检查- 通过后
send_notification_email→deliver_later→ 增加邮件计数
Email 限额
- AccountEmailRateLimitable concern:
- 日限额来源优先级:account.limits.emails → GlobalConfig ACCOUNT_EMAILS_LIMIT → ChatwootApp.max_limit
- 使用 Redis 计数(key:
ACCOUNT_OUTBOUND_EMAIL_COUNT:{account_id}:{date}) - TTL: 25小时
- 仅 ChatwootApp.chatwoot_cloud? 时生效
9. 通知Snooze与去重
功能描述
Snooze 功能允许用户将通知暂时延后处理,到期后自动恢复为未读。去重机制确保同一用户对同一 primary_actor(对话)不会产生多条冗余通知。
Snooze
- API:
POST /api/v1/accounts/{account_id}/notifications/{id}/snooze - 设置
snoozed_until时间戳 - Notification::ReopenSnoozedNotificationsJob(定时任务):
- 查询 snoozed_until 在过去3天至当前时间之间的通知
- 将其 snoozed_until 清空、read_at 清空、meta 记录 last_snoozed_at
- 更新 last_activity_at 和 updated_at 使通知重新出现在未读列表
去重
- Notification::RemoveDuplicateNotificationJob:
- 创建通知后异步执行
- 查找同一 user_id + primary_actor_id 的所有通知
- 保留最新一条(
order(created_at: :desc).first),删除其余 - 防止同一对话反复触发时产生重复通知
10. Webhook(Account Webhook)
功能描述
Webhook 是 Chatwoot 向外部系统推送事件数据的标准机制。账户级 Webhook 由管理员配置 URL + 订阅事件列表,当匹配事件发生时,WebhookListener 构建 payload 并通过 WebhookJob 异步发送 HTTP POST 请求。
用户操作流程
- 管理员进入 Settings → Integrations → Webhooks
- 点击 "Add Webhook",填写 URL、名称、订阅事件列表
- 可选择关联到特定 Inbox(inbox_type webhook)或账户全局(account_type webhook)
- 创建后系统自动生成 secret 用于签名验证
- 可通过 "Reset Secret" 重新生成签名密钥
涉及的API端点
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/accounts/{account_id}/webhooks |
列出账户所有 Webhook |
| POST | /api/v1/accounts/{account_id}/webhooks |
创建 Webhook |
| PATCH | /api/v1/accounts/{account_id}/webhooks/{id} |
更新 Webhook |
| DELETE | /api/v1/accounts/{account_id}/webhooks/{id} |
删除 Webhook |
- 创建请求:
{ webhook: { url, name, inbox_id, subscriptions: [...] } }
涉及的数据模型 + 关键字段
- Webhook(
webhooks表):id(bigint, PK)url(text) — Webhook 接收端 URL(唯一,同账户下不可重复)name(string) — Webhook 名称secret(string) — HMAC 签名密钥(has_secure_token 自动生成,支持加密存储)subscriptions(jsonb) — 订阅事件列表(数组)webhook_type(integer) — enum: account_type(0), inbox_type(1)account_id(integer, FK)inbox_id(integer, FK, optional) — inbox_type webhook 关联的收件箱created_at,updated_at
- 唯一索引:
index_webhooks_on_account_id_and_url - WebhookSecretable concern:提供
has_secure_token :secret+encrypts :secret+reset_secret!
允许的订阅事件(ALLOWED_WEBHOOK_EVENTS)
- conversation_status_changed
- conversation_updated
- conversation_created
- contact_created
- contact_updated
- message_created
- message_updated
- webwidget_triggered
- inbox_created
- inbox_updated
- conversation_typing_on
- conversation_typing_off
签名机制
- 请求头包含:
Content-Type: application/jsonAccept: application/jsonX-Chatwoot-Delivery: {uuid}— 投递唯一IDX-Chatwoot-Timestamp: {unix_timestamp}— 时间戳X-Chatwoot-Signature: sha256={HMAC-SHA256(secret, timestamp.body)}— 签名
11. IntegrationHook(集成Hook)
功能描述
Integrations::Hook 是第三方集成的执行单元,每个 Hook 关联一个 app_id(如 slack, dialogflow, openai, notion, leadsquared, linear 等),绑定到账户级或收件箱级。Hook 接收事件后通过 HookJob 路由到对应集成处理器。
涉及的API端点
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/accounts/{account_id}/integrations/hooks |
创建 Hook |
| PATCH | /api/v1/accounts/{account_id}/integrations/hooks/{id} |
更新 Hook(status, settings) |
| POST | /api/v1/accounts/{account_id}/integrations/hooks/{id}/process_event |
手动触发 Hook 处理事件 |
| DELETE | /api/v1/accounts/{account_id}/integrations/hooks/{id} |
删除 Hook |
- 创建请求:
{ hook: { app_id, inbox_id, status, settings: {...} } }
涉及的数据模型 + 关键字段
- Integrations::Hook(
integrations_hooks表):id(bigint, PK)app_id(string) — 集成应用标识(slack, dialogflow, openai, notion, leadsquared, linear 等)account_id(integer, FK)inbox_id(integer, FK, optional) — inbox hook 必填,account hook 可为空hook_type(integer) — enum: account(0), inbox(1),根据 app.params[:hook_type] 自动设置status(integer) — enum: disabled(0), enabled(1)access_token(string) — OAuth token(加密存储,可重授权)settings(jsonb) — 集成配置(如 Slack channel_id, Dialogflow project_id 等)reference_id(string) — 外部引用IDcreated_at,updated_at
- 验证:
account_id必填app_id必填inbox_id在 inbox hook 时必填settings需通过 app.params[:settings_json_schema] 的 JSON Schema 验证app_id在同一账户下唯一(除非 app.params[:allow_multiple_hooks])- feature_flag 需在账户中启用
- openai 需验证 API Key 有效性
涉及的业务逻辑
- HookJob(MutexApplicationJob,队列 medium):
- INTEGRATION_PROCESSORS 路由表:
- slack → process_slack_integration → SendOnSlackJob / UpdateSlackMessageJob
- dialogflow → process_dialogflow_integration → DialogflowJob
- google_translate → google_translate_integration
- leadsquared → process_leadsquared_integration_with_lock
- linear → process_linear_integration
- 跳过 disabled 的 hook
- 使用 Mutex 防并发
- INTEGRATION_PROCESSORS 路由表:
集成列表(从 apps.yml 加载)
- Slack、Dialogflow、Google Translate、Leadsquared、Linear、Notion、OpenAI(已迁移至 Captain)
12. WebhookListener — 事件驱动的Webhook投递
功能描述
WebhookListener 是异步事件监听器,订阅 AsyncDispatcher 中的事件,构建 payload 并投递到账户级 Webhook 和 API Inbox Webhook。
监听事件列表
| 事件方法 | 事件名 | Payload内容 | 投递目标 |
|---|---|---|---|
| conversation_status_changed | conversation.status_changed | conversation.webhook_data + event + changed_attributes | account webhooks + api inbox webhooks |
| conversation_updated | conversation.updated | 同上 | 同上 |
| conversation_created | conversation.created | conversation.webhook_data + event | 同上 |
| message_created | message.created | message.webhook_data + event(需 webhook_sendable?) | 同上 |
| message_updated | message.updated | 同上 | 同上 |
| webwidget_triggered | webwidget.triggered | contact_inbox.webhook_data + event + event_info | 同上 |
| contact_created | contact.created | contact.webhook_data + event | 仅 account webhooks |
| contact_updated | contact.updated | 同上 + changed_attributes | 同上 |
| inbox_created | inbox.created | inbox push_data + event | 仅 account webhooks |
| inbox_updated | inbox.updated | 同上 + changed_attributes | 同上 |
| conversation_typing_on/off | conversation.typing_* | event + user + conversation + is_private | inbox webhooks + api inbox |
投递逻辑
- deliver_account_webhooks:遍历
account.webhooks.account_type,过滤匹配 subscriptions 的 webhook,调用 WebhookJob - deliver_api_inbox_webhooks:仅 Channel::Api 类型且 webhook_url 不空的 inbox,调用 WebhookJob
- deliver_webhook_payloads:同时投递上述两种
- WebhookJob 参数:url, payload, webhook_type(:account_webhook/:api_inbox_webhook/:agent_bot_webhook), secret, delivery_id
13. HookListener — 集成Hook执行
功能描述
HookListener 监听事件并执行 IntegrationHooks(集成 Hook)。不同于 WebhookListener 直接 HTTP POST,HookListener 路由到各集成的专有处理器(如 Slack 发消息、Dialogflow AI回复等)。
监听事件列表
| 事件方法 | 事件名 | 执行逻辑 |
|---|---|---|
| message_created | message.created | 对所有 account.hooks 执行(过滤 inbox 匹配 + 事件支持) → HookJob |
| message_updated | message.updated | 同上 |
| contact_created | contact.created | 仅执行 account_hooks(无 inbox 限制) |
| contact_updated | contact.updated | 同上 |
| conversation_created | conversation.created | 仅执行 account_hooks |
| conversation_resolved | conversation.resolved | 仅在 status == resolved 时执行 account_hooks |
执行逻辑
- execute_hooks:遍历 message.account.hooks,过滤 inbox 不匹配的和事件不支持的,调用 HookJob.perform_later
- execute_account_hooks:遍历 account.hooks.account_hooks,过滤事件不支持,调用 HookJob.perform_later
- supported_hook_event?:检查 hook.app.params[:hook_events] 是否包含当前事件名
14. InstallationWebhook(平台级事件推送)
功能描述
InstallationWebhookListener 是平台级(跨账户)事件监听器,仅在 InstallationConfig 中配置了 INSTALLATION_EVENTS_WEBHOOK_URL 时生效。用于 SaaS/Cloud 部署场景下将平台级事件(如账户创建)推送到外部系统。
监听事件
- account_created:当新账户创建时,推送 payload = account.webhook_data + event + users(管理员列表)
- 投递:WebhookJob.perform_later(webhook_url, payload)
配置
INSTALLATION_EVENTS_WEBHOOK_URL(InstallationConfig)— 接收端 URL- 仅在 URL 存在时投递
15. 事件分发体系(Dispatcher架构)
功能描述
Chatwoot 采用 Wisper 发布/订阅模式实现事件分发。Dispatcher 是全局单例,在应用初始化时加载所有 Listener。事件分为同步和异步两种分发路径:
架构层次
Dispatcher (Singleton)
├── SyncDispatcher (同步)
│ └── listeners: [ActionCableListener, AgentBotListener]
│ └── dispatch → Events::Base → publish → 同步回调
└── AsyncDispatcher (异步)
│ └── listeners: [AutomationRuleListener, CampaignListener, CsatSurveyListener,
│ HookListener, InstallationWebhookListener, NotificationListener,
│ ParticipationListener, UnreadCounts::Listener, ReportingEventListener,
│ WebhookListener]
│ └── dispatch → EventDispatcherJob(critical队列) → publish_event → 异步回调
事件触发方式
- 业务代码中调用
Dispatcher.dispatch(event_name, timestamp, data) - 例:
Rails.configuration.dispatcher.dispatch(CONVERSATION_CREATED, Time.zone.now, conversation: conversation) - Dispatcher.dispatch 同时触发 Sync 和 Async 两条路径
Events::Base
- 属性:name, timestamp, data
method_name:将事件名conversation.created转为conversation_created方法名- Listener 中对应方法接收
Events::Base对象
事件类型定义(Events::Types)
- 安装级事件:ACCOUNT_CREATED, ACCOUNT_CACHE_INVALIDATED
- 对话事件:CONVERSATION_CREATED/UPDATED/DELETED/READ/BOT_HANDOFF/OPENED/RESOLVED/STATUS_CHANGED/CONTACT_CHANGED/TYPING_ON/OFF/MENTIONED
- 分配事件:ASSIGNEE_CHANGED, TEAM_CHANGED
- 消息事件:MESSAGE_CREATED, FIRST_REPLY_CREATED, REPLY_CREATED, MESSAGE_UPDATED
- 联系人事件:CONTACT_CREATED/UPDATED/DELETED/MERGED
- 通知事件:NOTIFICATION_CREATED/UPDATED/DELETED
- Inbox事件:INBOX_CREATED/UPDATED
- WebWidget事件:WEBWIDGET_TRIGGERED
跨功能依赖
| 本模块功能 | 依赖的其他模块 |
|---|---|
| 通知创建 | M3 Conversation(primary_actor)、M1 User(user/secondary_actor) |
| ActionCable 广播 | M2 Inbox(inbox.members 确定广播目标)、M3 Conversation(push_event_data) |
| Webhook 投递 | M3 Conversation/Message(webhook_data)、M4 Contact(webhook_data) |
| Hook 执行 | M2 Inbox(hook.inbox)、M6 Automation(HookJob 路由) |
| Email 限额 | M1 Account(limits.emails)、AccountEmailRateLimitable |
| Push 订阅 | M1 User(notification_subscriptions) |
| 通知权限 | M5 Team(inbox membership、conversation access) |
GoChat 实现建议
| 功能 | 建议 |
|---|---|
| Notification 模型 | 保留 polymorphic actor 设计,但建议将 notification_type 改为 string enum(Go 没有 integer enum 习惯) |
| NotificationSetting | 用 JSON/map 存储各通知类型开关,而非位运算(Go 无 FlagShihTzu 对应) |
| ActionCable | 替换为 WebSocket/gorilla 或 nats/gorilla pub-sub,前端用相同事件名协议 |
| Push 通知 | 浏览器 Push 用 webpush-go,FCM 用 firebase-admin-go,可选 ChatwootHub 模式 |
| Email 通知 | 使用模板引擎(如 mailgun/sendgrid template),邮件限额用 Redis 计数 |
| Webhook | 保留签名机制(HMAC-SHA256),HTTP POST 用带超时的 SafeFetch 模式 |
| Hook 路由 | 用接口/策略模式替代 HookJob 的 INTEGRATION_PROCESSORS 硬编码路由 |
| Dispatcher | 替换 Wisper 为 Go channel/event-bus,同步/异步用 goroutine+worker pool |
| Snooze | 保留定时任务恢复机制,可用 cron 或 tick scheduler |
| 去重 | 保留 RemoveDuplicate 模式,创建后异步清理 |