协作中的 Webhook
您可以定义一个 URL,我们将在每次文档更改时调用它。这对于在您自己的应用程序中获取 Yjs 文档的 JSON 表示是非常有用的。
当文档保存到我们的数据库时,我们会调用您的 webhook URL。此操作具有 2-10 秒的防抖延迟。因此,我们不会淹没您的应用程序。目前,我们仅导出 Yjs 文档的 default 片段。
配置 Webhooks
要为文档和评论通知配置 webhooks,您可以按照以下步骤操作:
- 导航到您账户中的 协作设置。
- 找到 webhooks 部分并添加您希望的端点 URL。
添加 URL 后,webhook 会立即生效。您将开始接收指定事件的通知,无需任何延迟。
为您的 webhook 添加评论支持
如果您想为评论功能添加 webhook 支持,并且您的文档服务器是在 2024 年 3 月之前创建的,请按照 下面的说明 升级您的 webhook。
示例有效载荷
所有请求到您的 webhook URL 的请求将包含一个名为 X-Hocuspocus-Signature-256 的头,它使用您的密钥对整个消息进行签名。您可以在您的 Tiptap Collab 应用的 设置 中找到该密钥。
{
"appName": "", // 您的文档服务器名称
"name": "", // 文档名称(如果需要,经过 URI 编码)
"time": 0, // 当前时间的 ISO 字符串(Date.getTime())
"tiptapJson": {}, // 来自 Tiptap 的 JSON 输出(见 https://tiptap.dev/guide/output#option-1-json):TiptapTransformer.fromYdoc()
"ydocState"?: {}, // 可选,包含整个 yDoc 的 base64 编码。此项可在运行时配置中启用(见 https://tiptap.dev/docs/collaboration/operations/configure 中的 `webhook_include_ydoc_state`)
"clientsCount": 100, // 当前连接客户端数量
"type": "", // 有效载荷类型(如果文档已更改,则为 DOCUMENT);仅在您使用 webhooks v2 时可用
"trigger": "", // 触发事件的原因(通常是 "document.saved");仅在您使用 webhooks v2 时可用
"changedFields": [], // 内容发生变化的文档字段名称(见下文)
"users": [] // 自上一个 webhook 以来更改内容的用户列表(JWT 的 "sub" 字段)
}内容变化
document.saved 会在文档每次写入数据库时触发,但并非每次写入都是内容变化。最常见的例子是:当你向
TiptapCollabProvider 传入 user 时,提供程序会在每次连接时将该客户端记录到文档中,因此即使没有人输入任何内容,
仅打开文档也会产生保存操作,进而触发 webhook。
有两种方式可以区分这两种情况。
changedFields 数组
每个 document.saved 和 content.changed 有效载荷都会列出内容与上次报告的变化不同的文档字段:
{
"changedFields": ["default"]
}如果打开文档但未编辑,得到的就是 "changedFields": [],因为提供程序写入的客户端记录属于内部字段,不计入内容。
所有 webhook 版本都会发送 changedFields。
空数组不代表一定没有变化
当没有内容变化时,changedFields 为空;但当服务器无法确定发生了什么变化时它也为空,例如文档从未加载就被写入。
请将空数组理解为“未报告变化”,而不是“没有变化”。如果你需要一个可以据此执行操作的信号,请使用下面的
content.changed:只有在确认存在内容变化时才会发送该事件。
content.changed 事件
content.changed 是一个单独的触发事件,会发送到同一个 webhook URL,并携带与 document.saved 相同的有效载荷,
但仅在内容确实发生变化时发送。如果你的集成目前监听 document.saved 并忽略不包含编辑的保存操作,可以改为订阅
content.changed,不再执行这项检查。
它还覆盖了 document.saved 无法覆盖的一种情况:如果观察到编辑的服务器在计划保存前关闭,另一台服务器会持久化内容,
并将其报告为 content.changed。该次保存不会发送 document.saved。
有两个要求:
- 将
webhook_version设置为2或更高版本。版本 1 的有效载荷不包含trigger字段,因此无法区分这两个事件。 - 使用
webhook_events显式订阅该事件,详见下文。
选择接收哪些事件
webhook_events 是一个以逗号分隔的允许列表,用于指定要接收的触发事件:
| 值 | 结果 |
|---|---|
| 未设置 | 除 content.changed 之外的所有事件,这是此设置存在之前端点收到的事件集合 |
document.saved,content.changed | 仅这两个事件 |
* | 所有事件,包括未来版本新增的事件 |
none | 不接收任何事件 |
无法识别的条目会被忽略。如果某个值只包含无法识别的条目,则不会回退到默认值,而是什么都不发送;这样拼写错误会明确失败, 不会悄悄恢复为接收全部事件。
可用事件:document.saved、content.changed、user.connected、user.disconnected、thread.added、
thread.updated、thread.resolved、thread.deleted、comment.added、comment.updated、comment.deleted、
document.reverted、version.created。
选择哪些字段计入内容
默认情况下,所有不是 Tiptap Collaboration 内部字段的字段都会计入内容,也就是名称不以 __tiptapcollab__ 开头的字段。
设置 content_fields 可改变这一行为:
| 值 | 结果 |
|---|---|
| 未设置 | 所有不带 __tiptapcollab__ 前缀的字段 |
default | 仅主编辑器字段 |
default,body | 这两个字段 |
body* | 每个条目中的一个 * 表示 glob,因此匹配此前缀的所有字段 |
__tiptapcollab__threads | 将评论活动计为内容变化 |
* | 所有字段,包括内部字段 |
该值会替换默认规则,而不是缩小默认规则;因此你可以将 __tiptapcollab__threads 这类内部字段重新加入统计范围。
由于这些是 Yjs 字段名,条目区分大小写。
修改此设置会在下一次保存时生效。刚添加到集合中的字段尚无记录状态,因此会报告一次变化,之后恢复稳定。移除字段本身不算变化, 它表示“停止监视此字段”。
重试
默认情况下,webhooks 不会重试,但您可以通过将 webhook_retries 设置为 1 来启用重试(见 配置运行时)。
重试计划如下:
- 第一次重试:初次尝试后 5 秒
- 第二次重试:上一次尝试后 15 秒
- 第三次重试:上一次尝试后 2 分钟
- 第四次重试:上一次尝试后 10 分钟
- 第五次重试:上一次尝试后 30 分钟
- 第六次重试:上一次尝试后 3 小时
所有重试都包含一个头 X-Hocuspocus-Retry,其中包含当前的重试计数。有效载荷中的 time 属性是初始尝试的时间戳。
启用评论 webhook
支持评论的 webhook 自动为所有在 2024 年 3 月之后创建账户的用户启用。
如果您的账户是在 2024 年 3 月之前创建的,并且您正在使用旧版本的 webhook 系统,则需要手动启用新的评论 webhook。操作方法如下:
- 如果您已经实现了之前的协作 webhook,请确保在处理传入 webhooks 时检查
type和trigger字段。 - 导航到您账户中的 协作设置。
- 找到 Webhook 部分并点击“更新”按钮。
此升级是为了适应多个新事件路由到同一个 webhook 端点的引入,由新的 type 和 trigger 字段区分。
如果您不希望使用评论 webhook,则无需进行升级。
加载器 Webhook
为了初始化文档,您可以使用 webhook_loader_url 设置(见 配置运行时)。如果请求新的文档,将调用此 URL。
该 webhook 将包含一个头 Authorization,其中包含您的密钥,以及一个 document-name,其中包含请求文档的名称。
如果您返回一个 yjs 更新(在您的端使用 Y.encodeStateAsUpdate),它将应用于文档。如果您返回其他内容,文档将以空文档初始化。 请注意,加载器 webhook 仅在文档创建时调用一次。
觉知 Webhooks
如果您希望在用户连接到或断开与文档的连接时收到通知,可以在 这里 启用觉知 Webhooks。
如果您需要用户参数,请确保将其传递给 TiptapCollabProvider,如 这里 所述。
事件如下所示:
{
"trigger": "user.connected", // 或者 user.disconnected
"user": "user_1",
"numConnectedUsers": 0,
"appName": "",
"name": "testdocument",
"time": "2025-04-21T19:32:55.632Z",
"type": "DOCUMENT"
}版本 Webhooks
当创建新版本时,会向您配置的 webhook URL 发送一个 version.created webhook。有效载荷包含版本的元数据,包括自动生成的 __tiptap 元数据。
{
"trigger": "version.created",
"type": "DOCUMENT",
"appName": "",
"name": "testdocument",
"time": "2026-03-09T12:00:00.000Z",
"version": 5,
"versionName": "版本名称",
"meta": {
"__tiptap": {
"trigger": "websocket",
"changesBy": ["#user1", "#user2"],
"triggeredBy": "user1"
},
"wordCount": 4523
}
}关于 meta.__tiptap 字段的更多细节,请参见 自动版本元数据。
自定义字段
如果您在 Yjs 中使用自定义字段,可以通过在 运行时配置 中设置 webhook_include_fields=1 将它们添加到 webhook。
为了让我们识别 yjs 字段的正确类型,您需要在 Tiptap Typemap 中配置它们。
// 在 Yjs 中存储任何数据。参见官方文档:https://docs.yjs.dev/api/shared-types
// 这里的 "config" 可以替换为任何字符串。map 给您一个键值存储。
const ydoc = new Y.Doc()
ydoc.getMap('config').set('mycustomoption', 'value123');
// 在 provider 中,您需要告诉我们 yjs 类型。
// 我们目前支持的可能选项有:map、array、text、xmlfragment、xmlelement
const provider = new TiptapCollabProvider({document: ydoc})
provider.setFieldType('config', 'map');