Webhook

最后更新:2026年7月3日

设想您经营一家服装网店。每次上架新商品时,都有一些必须亲手处理的后续工作。比如把商品说明翻译成其他国家的语言,或是把上架这件事通知到公司内部的即时通讯工具里。这些后续工作不必每次都靠人手去做,您可以让系统在商品上架的那一刻,自动通知外部的某个程序来代为处理。这种"某件事发生时就自动通知到预先指定的地方"的装置,就是 Webhook

可以把它比作装在店门上的门铃。客人推门进来时(商品被上架时),门铃会自动响起,里面的店员(外部程序)一听到"有客人来了"就立刻开始行动。不需要有人一直守在门口盯着。Webhook 就像那只门铃,在预定的事件发生的那一刻,向预定的地方发送请求。

本页先来看 Webhook 是什么、在什么情况下使用,然后在服装店 Space 里亲手创建一个 Webhook。同时还会介绍如何把外部程序返回的结果再填回到商品里的 WriteBack

Webhook 做的事

Webhook 由预先设定的三件事构成。

  • 何时发送:设定在什么事件发生时发送请求。例如可以设为"商品(Content)被新建时"。
  • 发送到哪里:设定接收请求的外部程序的地址。填入一个互联网地址(URL)即可。
  • 开启还是关闭:设定现在要开启这个 WebhookACTIVE),还是暂时关闭它(INACTIVE)。关闭后,即使预定的事件发生,也不会发送请求。

当预定的事件实际发生时,Webhook 会向填入的地址发送请求,告知这件事。请求中会带上诸如发生了什么事、是在哪个商品上发生的之类的信息。收到请求的外部程序会根据这些信息去做自己的工作。

哪些变化会触发请求

触发请求的"事件",是 Space 内的资源所发生的变化。您可以选择诸如商品这样的 Content、上传的文件 Media、作为表单模板的 Content Type 上发生某事时的情况。

每种资源可选的变化如下。

变化何时发生服装店示例
Create被新建时上架新商品
Save修改内容并保存时修改并保存商品说明
Delete被删除时删除已停产的商品
Publish发布并对外公开时把商品公开到网站
Unpublish取消发布时把缺货商品从网站撤下
Archive进行归档时把过季商品归档
Unarchive解除归档时把归档的商品恢复

例如"每当有新商品上架时就发送请求",就是选择"商品(Content)的 Create"。

一个 Webhook 也可以同时选择多种变化。如果同时选择"商品被新建时"和"商品被修改时",那么这两者中任何一个发生,请求都会发出。

加条件来收窄范围

有时即使选中的变化发生了,您也不希望每次都发送请求。例如您可能只想在"不是所有 Content,而是用'商品'表单创建的 Content 被新建时"才接收。这时就用筛选来收窄发送请求的情形。

一条筛选由"以什么为基准、如何比较"这样一行构成。以什么为基准来过滤,要从四项中选择。

  • 是用哪个表单创建的项目:例如只向用"商品" Content Type 创建的 Content 发送请求。这是最常用的条件。
  • 是否为某个特定项目:只对在某个指定项目上发生的变化发送请求。
  • 是谁创建的项目:只对特定的人创建的项目发送请求。
  • 是谁最后修改的项目:只对特定的人最后修改过的项目发送请求。

同时也要选择比较的方式。可以收窄为:仅当与设定值相等时、仅当不相等时、仅当属于设定的多个值之一时、仅当不属于其中任何一个时,或仅当符合或不符合设定格式(模式)时。

在内容工作室的触发器设置中,用 + 添加筛选逐行添加条件。设置多条筛选时,只有全部满足这些条件的情况才会发送请求;一条都不设时,则每当选中的变化发生时就发送请求。

按外部程序想要的格式发送

如果不另行设定,请求中会原样带上发生变化的那个项目的全部信息。例如保温杯商品被新建时,请求中带去的内容大致是这样的形状。

{
  "sys": { "id": "3trmXRM3RqbgSnifyg7OGhwhlqvAvq", "type": "Content" },
  "fields": {
    "productName": { "ko-KR": "스테인리스 텀블러 500ml" }
  }
}

(实际上会带上更多信息,上面只是摘取的一部分。)外部程序从中挑选自己需要的值来用即可。但也有一些程序规定了"只接收这种格式"。这种情况下,就在内容工作室的 Payload 中选择自定义 Webhook payload,自己写下要发送的格式。

写下要发送的格式时,在需要从上面的数据中取值填入的地方使用占位符。占位符的形状为 { /payload/… }。这里的 payload 指的就是上面展示的那个项目整体,其后的路径用来精确定位想要的值。

  • { /payload/sys/id } → 上面数据中 sys 内的 id(商品的唯一编号)
  • { /payload/fields/productName/ko-KR }fieldsproductNameko-KR(韩语商品名)。fields/ 之后依次接 Field 的 ID(商品名即 productName)和语言代码(韩语即 ko-KR)。

例如翻译程序要求"按这种格式给出待翻译的文本和商品编号",那么就这样写 payload。

{
  "id": "{ /payload/sys/id }",
  "text": "{ /payload/fields/productName/ko-KR }"
}

这样一来,保温杯商品被新建的那一刻,占位符会被替换为实际的值,按如下形式发送。

{
  "id": "3trmXRM3RqbgSnifyg7OGhwhlqvAvq",
  "text": "스테인리스 텀블러 500ml"
}

同样的占位符也可以放进发送地址(URL)或请求头的值里,发送的方式(method)和格式(JSON 或表单格式)也可以一并选择。如果指向的路径上没有值,那个位置就会变成空值。

像外部 API 密钥这种不能让别人看到的值,在添加请求头时把它的类型设为 Secret。这样该值就会被遮蔽后保存,不会暴露给最终用户。

用收到的响应回填:WriteBack

如果说上一节是设定要发送的请求的格式,那么 WriteBack 处理的是返回的响应Webhook 调用外部程序并收到正常(2xx)响应时,可以对该响应进行加工,从而在 Space 内创建、修改 ContentMedia,或对其进行发布、删除。人工复制结果再粘贴的过程就此消失。

我们把服装店的翻译示例一路跟到底。前面已经把新商品的韩语商品名发给了翻译程序。假设翻译程序这样响应。

{ "translated": "Stainless Tumbler 500ml" }

接下来配置一个 WriteBack,把这段译文填入同一商品的英文商品名栏。在内容工作室的 WriteBack 中,用 + 添加操作添加一个 ContentUpdate(修改)动作,并这样设定。

  • 目标:如果不另行指定,发生变化的那个商品(保温杯)就是目标。
  • 要填的栏和语言:因为是英文商品名,所以栏为商品名(productName),语言为英语(en-US)。
  • 要填的值:指向响应中译文的 { /response/translated }

这样一来,保温杯商品的英文商品名栏就会被填入 Stainless Tumbler 500ml

这样的 WriteBack 在与外部 LLM 对接时尤其有用。例如可以用一个 Webhook 构成这样的流程:把商品说明发出去生成宣传图片并保存为 Media,或用生成的简介文案创建新的 Content

执行顺序

  1. 发生变化时,Webhook 按设定的格式向外部程序发送请求。
  2. 若响应为 2xx,则从上到下依次执行已添加的 WriteBack 动作(operation)。
  3. 各个动作彼此独立,即使其中一个失败,其余的也会继续执行。
  4. WriteBack 产生的创建、修改、删除、发布同样会被捕捉为变化,从而可以接连触发订阅了该变化的其他 Webhook。无止境循环的情况会在创建或修改 Webhook 时被自动检测并阻止。

WriteBack 创建或改变的资源,其创建者(createdBy)即为引发该变化的用户。

值表达式

要填的值用前面看到的那种占位符({ /… })来获取。不过在 WriteBack 中,可指向的根有两个

指向的对象
{ /response/… }外部程序返回的响应正文
{ /payload/… }发生变化的原始项目Content/Media
  • 仅单独使用一个占位符时({ /response/url }),会原样获取值的原本形态(文本、数字、对象)。
  • 与文本混用时("已创建: { /payload/sys/id }"),会拼接成一段文本。
  • 当响应不是 JSON 而是纯文本时,用 { /response } 把整个响应作为文本接收(无法指向其中的下级路径)。

处理 Content($content

用响应来创建或改变 Content 的动作。需设定的项目如下。

项目说明
actionCreateUpdateDeletePublishUnpublishArchiveUnarchive 中的一个(不区分大小写)
contentType要创建的 ContentContent TypeCreate 时必填,只需有 sys.id 即可。
target要改变的目标 Content 的 ID(用占位符指定)。省略时,发生变化的那个项目即为目标。(UpdateDeletePublishUnpublishArchiveUnarchive
locale记录 fields 值的语言。代码(例如 ko-KR)或可解析为该代码的占位符。省略时为 Space 的默认语言。(CreateUpdate
fields栏名 → 要填的值(占位符)。(CreateUpdate
publishCreateUpdate 后是否立即发布(默认开启)。关闭则不发布,保留为 Draft
unpublishDelete 时是否先自动取消发布再删除(默认开启)。关闭后,若目标尚未处于可删除的状态,则删除会失败。

处理 Media($media

把响应中收到的文件导入为 Media,或改变 Media 状态的动作。

项目说明
actionCreateDeletePublishUnpublishArchiveUnarchive(没有修改动作 Update
source指向要导入的文件的占位符。(Create
encoding接收文件的方式:Url(从地址下载)或 Base64(把响应中携带的文件数据解码后保存)
locale记录导入的文件及标题、说明的语言。与 Content 相同的规则。(Create
titledescription该语言的媒体标题、说明(占位符,可选)。(Create
target要改变的目标 Media 的 ID。省略时为发生变化的那个项目。(DeletePublishUnpublishArchiveUnarchive
publishCreate 后处理完成时是否立即发布(默认开启)。
unpublishDelete 时是否先取消发布再删除(默认开启)。

$media 也可以用作 Content 的栏值。把响应中收到的文件导入为 Media 后,直接把该 Media 连接到栏上,就能一次性创建出"包含图片的 Content"。

MediaDeleteArchiveUnarchive 不仅处理列表上的信息,还会一并处理存储中的实际文件。

设置方法与示例

在内容工作室的 WriteBack 中,用 + 添加操作逐个添加动作。您可以在屏幕上填写各项的 Visual 方式和直接编写下面这种 writeBacks 块的 JSON 方式之间切换。无论用哪种,结果都一样。

把外部 LLM 生成的图片附上去,创建新商品。 把响应中携带的图片地址导入为 Media,从而创建一个拥有该图片的 Content

{
  "writeBacks": [
    {
      "$content": {
        "action": "Create",
        "contentType": { "sys": { "id": "<商品 Content Type 的 sys.id>" } },
        "fields": {
          "productName": "{ /payload/fields/productName/ko-KR }",
          "photo": { "$media": { "source": "{ /response/data/0/url }", "encoding": "Url" } }
        }
      }
    }
  ]
}

增补发生变化的那个商品。 省略 target 时,发生变化的 Content 即为目标。把响应中的值填入该商品的栏。

{
  "writeBacks": [
    {
      "$content": {
        "action": "Update",
        "locale": "en-US",
        "fields": { "productName": "{ /response/translated }" }
      }
    }
  ]
}

为了审核后再发布而只创建为 Draft 关闭 publish 后就不发布,而是保留为草稿,可在人工确认后再发布。

{
  "writeBacks": [
    {
      "$content": {
        "action": "Create",
        "contentType": { "sys": { "id": "<Content Type 的 sys.id>" } },
        "fields": { "text": "{ /response/choices/0/message/content }" },
        "publish": false
      }
    }
  ]
}

把响应图片导入为独立的 Media 用地址接收时把 encoding 设为 Url,用响应中携带的文件数据接收时设为 Base64

{
  "writeBacks": [
    { "$media": { "action": "Create", "source": "{ /response/data/0/url }", "encoding": "Url" } }
  ]
}

DeletePublishUnpublishArchiveUnarchive 形状相同。只需写上 action 和要改变的目标 target,就只改变该目标的状态(省略目标时为发生变化的那个项目)。例如要发布响应所告知的 Content,就这样写。

{
  "writeBacks": [
    { "$content": { "action": "Publish", "target": "{ /response/contentId }" } }
  ]
}

添加多个动作时,会按写入的顺序执行。比如可以这样接连排布:"把翻译结果填入商品,接着把宣传图片导入为 Media"。

查看执行结果

各个 WriteBack 动作的执行情况,可在 Webhook 的调用记录中查看。每个动作都会留下以下内容。

项目说明
顺序在已添加的动作中是第几个
目标Content 还是 Media
动作执行的 action
状态Success(成功)、 Failed(失败)、 Skipped(跳过)
结果 ID被创建或改变的资源的 ID(有时)
错误失败时的消息

成功的动作可以通过结果 ID 追踪到哪个资源被创建或改变了。如果响应不是 2xx,WriteBack 根本不会执行,结果记录也会是空的。

需要知道的事

  • 触发请求的变化只设一个。 如果一个 Webhook 同时捕捉"新建(Create)"和"发布(Publish)",外部程序可能被调用两次。使用 WriteBackWebhook 请只选择一个变化。
  • 只能搬运值,不能做运算。 它只做到从响应中取值填入为止,不进行条件分支、循环之类的处理。
  • 不会重试。 处理过程中一旦出问题,那个动作可能就此丢失。
  • 不要让人随意创建。 对于只由 WriteBack 创建的 Content Type,最好收窄权限,使普通用户无法直接创建。

创建服装店 Webhook

现在来在服装店 Space 里创建一个 Webhook。这是一个"新商品被上架时,就把这件事通知给预先准备好的外部翻译程序"的 Webhook。我们假设接收请求的外部程序的地址为 https://example.com/translate

  1. 在服装店 Space 的设置中打开 Webhook 画面。
  2. 按右上角的创建按钮。
  3. 在名称栏中输入 新商品翻译通知。这个名称是为了日后辨认这是哪个 Webhook
  4. 设定要发送请求的变化。如果只想对特定变化发送,就选择选择特定触发事件后指定想要的变化(这里是商品(Content)的 Create);如果想对所有变化都发送,就选择为所有事件触发
  5. URL 栏中输入接收请求的外部程序的地址 https://example.com/translate
  6. 开启启用后,创建完成的瞬间就会发送请求(ACTIVE)。如果只想暂时测试,就把它关闭(INACTIVE)。
  7. 创建按钮来创建 Webhook

新建 Webhook 画面。已填入名称、启用、触发器选择、URL 的样子

当列表中出现处于 ACTIVE 状态的 新商品翻译通知 时,Webhook 就创建好了。

Webhook 列表中显示处于 ACTIVE 状态的"新商品翻译通知"的画面

创建之后,请在服装店实际上架一个新商品试试。上架的那一刻,Webhook 就会向填入的地址发送请求。请求是否成功送达、外部程序如何响应,都可以在 Webhook 的调用记录中查看。

开启关闭与修改

Webhook 创建之后也可以随时开启和关闭。想暂时停止发送请求时,不要删除,而是把它关闭为 INACTIVE。关闭期间,即使上架新商品也不会发送请求。再次开启为 ACTIVE 后,就会从那时起重新发送请求。

重新打开已创建的 Webhook 后,可以关闭或重新开启启用。名称、要发送请求的地址、要触发的变化等内容日后也可以修改;不再使用的 Webhook 删除即可。

接下来要做的事

  • Content 建模:介绍如何创建"商品"这类 Content 的表单模板,它正是 Webhook 触发请求的对象。
  • 编写 Content:可以实际上架商品,确认 Webhook 是否正常运作。
  • API 参考:介绍在程序中直接创建和管理 Webhook 时所用的请求、响应格式和字段规范。