# Webhook
Webhook 用于让第三方系统主动通知本系统:平台为每个 Webhook 生成一个专用的调用地址,第三方系统按约定的 HTTP 方法调用这个地址,平台就执行对应的动态逻辑,并把逻辑的返回值作为 JSON 响应返回。
典型场景:电商平台在订单支付后推送订单通知、支付平台推送支付结果、设备网关推送告警等。
提示
1.0 起已移除旧的“系统集成”(Incoming/Outgoing 集成、Enable Logic、集成执行用户)。传入集成改用本页的 Webhook;传出集成没有直接的替代功能,迁移方法见 从传出集成迁移。
# 目标读者
本文档的目标读者为:本系统的开发和实施人员
# Webhook 列表
菜单位置:开发定制 > 系统集成 > Webhook

一个 Webhook 包含以下字段:
| 界面显示 | 属性名 | 说明 |
|---|---|---|
| Name | name | 名称,租户内唯一,创建后不可修改(编辑表单中为只读) |
| Description | description | 描述 |
| Url | url | 调用地址中的标识,创建时自动生成(UUID),租户内唯一 |
| Http method | httpMethod | 调用方式,每个 Webhook 只能选一种:GET 或 POST |
| Active | active | 是否激活 |
| Effective date | effectiveDate | 有效期开始时间,可以为空 |
| Expiry date | expiryDate | 有效期结束时间,可以为空 |
| Core logic | coreLogic | 收到调用时执行的动态逻辑 |
| Exec records | execRecords | 执行记录,见 执行记录 |
Webhook 只在同时满足以下条件时才会执行:Active 为开启;Effective date 为空或早于当前时间;Expiry date 为空或晚于当前时间。
列表中的 Http method 列当前显示为 -,这是当前版本的界面问题,实际值以创建时的请求为准。
# 创建 Webhook
当前版本的界面问题
在 1.0.0-beta18 中,新建和修改表单里的 Http method 下拉框加载不出选项,Url 又被表单标记为必填,所以暂时无法在界面上保存新的 Webhook(下面第 2 步的截图就是这个状态)。在平台修复之前,请按第 2 步之后的方法通过 REST API 创建。
- 在
开发定制 > 动态逻辑 > 动态逻辑中创建 Webhook 要执行的动态逻辑。平台没有 Webhook 专用的逻辑类型,逻辑类型可以任选,引擎选GROOVY_CODE。逻辑的写法见下文 注入变量 和 返回结果。 - 在 Webhook 列表页点击
创建,填写名称、描述、HTTP 方法,打开Active,选择上一步创建的核心逻辑后保存。保存时平台自动生成Url。

截图中 Http method 为空、Url 标着必填,点 保存并关闭 无法保存,这就是上面说的界面问题;弹窗标题显示为领域类名 DynamicWebhook,也是当前版本的界面问题。在平台修复之前,请通过 REST API 创建(先按 平台 API 登录拿到 token):
curl -X POST http://<服务器地址>/api/data/DynamicWebhook \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{"name":"接收订单通知","description":"电商平台在订单支付后推送通知","httpMethod":"POST","active":true,"coreLogic":{"id":<动态逻辑 id>}}'
2
3
4
响应中的 data.url 就是自动生成的地址标识。
# 调用地址
Webhook 的调用地址为:
http://<服务器地址>/api/webhook/<Url>
其中 <Url> 是列表中 Url 列的值。调用方式必须与 Http method 一致。Webhook 接口不需要登录,任何知道地址的人都可以调用,所以要把地址当作密钥保管,必要时 重新生成地址。
以“接收订单通知”为例(Http method 为 POST):
curl -X POST http://<服务器地址>/api/webhook/9a72bdc1-bc78-469d-83e3-8fa976afc4ce \
-H 'Content-Type: application/json' \
-d '{"orderNo":"SO20260923001","amount":1280.00}'
2
3
返回:
{"webhook":"接收订单通知","orderNo":"SO20260923001","received":true,"receivedAt":"2026-09-23T13:33:39.078338160Z[UTC]"}
# 注入变量
核心逻辑执行时,可以使用以下注入变量:
| 变量名称 | 变量类型 | 描述 |
|---|---|---|
webhook | tech.muyan.dynamic.integration.DynamicWebhook | 当前被调用的 Webhook |
headers | Map<String, String> | 请求头,键为小写,例如 headers['x-signature'] |
parameters | Map<String, Object> | 请求参数,见下方说明 |
requestTime | java.time.ZonedDateTime | 收到请求的时间 |
requestUrl | String | 后端收到的请求地址,不含 queryString,也不含 /api 前缀。主机名取自 nginx 转发的 Host,不带端口,例如 http://localhost/webhook/<Url> |
application | grails.core.GrailsApplication | 当前的 grails 应用上下文 |
log | Closure<?> | 用于打印执行日志的 log 闭包,日志写进执行记录 |
Http method为GET时,parameters是 URL 参数,类型为grails.web.servlet.mvc.GrailsParameterMap。除了调用方传的参数,里面还有平台路由附带的controller、action、webhookKey三个键(实测?orderNo=SO1&amount=10得到[orderNo:SO1, amount:10, controller:dynamicWebhook, action:get, webhookKey:<Url>]),遍历参数时要跳过它们。Http method为POST时,parameters是 JSON 请求体,类型为org.grails.web.json.JSONObject。调用方必须带Content-Type: application/json;表单编码(application/x-www-form-urlencoded)的请求体不会被解析,parameters为空。
注意
Webhook 接口不需要登录,逻辑里没有当前用户(没有 userContext)。如果需要校验调用方,可以约定一个请求头(例如签名),在逻辑里通过 headers 读取并校验,校验不通过时抛出异常。
# 返回结果
核心逻辑应返回 Map<String, Object>,平台把它转成 JSON 原样返回给调用方,HTTP 状态码为 200。逻辑中抛出异常时,平台返回 HTTP 500,响应体为异常信息文本。
“接收订单通知”使用的核心逻辑示例:
log("收到订单通知:${parameters}")
String orderNo = parameters.orderNo
if (!orderNo) {
throw new IllegalArgumentException('缺少订单号 orderNo')
}
// 在这里写业务处理,例如更新订单状态
return [
received : true,
webhook : webhook.name,
orderNo : orderNo,
receivedAt: requestTime.toString(),
]
2
3
4
5
6
7
8
9
10
11
12
# HTTP 状态码
以下是实测的各场景返回:
| 场景 | 状态码 | 响应体 |
|---|---|---|
找不到该地址,或调用方式与 Http method 不一致 | 404 | Webhook not found |
| Webhook 未激活或不在有效期内 | 500 | Webhook is not active or expired |
| 核心逻辑抛出异常 | 500 | 异常信息,例如 缺少订单号 orderNo |
| 核心逻辑执行成功 | 200 | 核心逻辑返回值的 JSON |
注意
请勿根据响应体中的提示文本做业务判断,这些文本不保证在平台升级后保持不变。
# 重新生成调用地址
调用地址泄露后,可以重新生成:在 Webhook 列表中勾选要处理的 Webhook,点击 重新生成传入集成地址,在确认框中点 确定。平台为选中的 Webhook 生成新的 Url,旧地址作废(最长有 10 分钟延迟,见 缓存与生效时间),所有调用方都要改用新地址。

确认框标题 Regenerate URL 未翻译,操作成功后的提示是半句话“已重新生成传入集成接口的”,这些都是当前版本的界面问题,不影响功能。
# 缓存与生效时间
平台按地址缓存 Webhook 定义,缓存在首次调用后 10 分钟过期。因此修改 Webhook 后(包括重新生成地址、关闭 Active、修改有效期),最长要等 10 分钟才生效。实测重新生成地址后,旧地址在缓存过期前仍然可以调用,之后才返回 404。发现地址泄露时,请把这 10 分钟考虑进去。
核心逻辑的代码修改不受这个缓存影响,保存后下一次调用即生效。
# 执行记录
除了地址找不到(404)的请求,每次调用都会生成一条执行记录,Webhook 未激活时也会记一条失败记录。在 Webhook 列表中点击某一行 Exec records 列的按钮,可以查看该 Webhook 的执行记录:

| 界面显示 | 说明 |
|---|---|
| Webhook | 对应的 Webhook。当前版本这一列显示的是 Webhook 的 id,不是名称 |
| Status | 执行结果:Success 或 Failed |
| Exec params | 执行参数(当前版本中为空,请求参数见 Exec result) |
| Start time | 开始时间 |
| Finish time | 结束时间 |
| Exec result | 请求方法、请求参数、请求头和核心逻辑的返回值;未激活时为提示信息。当前版本实测“Request headers”一栏为空 |
| Exec log | 逻辑中通过 log 打印的日志 |
| Stack Trace | 出错时的异常堆栈 |
执行记录 菜单下没有 Webhook 的入口,只能从 Webhook 列表进入。
注意
按平台源码的设计,执行结果会记录全部请求头,包括 Authorization、Cookie 和签名等请求头,并以明文保存在数据库中。当前版本实测这一栏为空,但平台修复后就会记录,所以仍然不要让调用方在 Webhook 请求中携带长期有效的凭据。
# 从传出集成迁移
旧版的传出集成(Outgoing)用于在对象创建、修改、删除时通知外部系统,1.0 起已移除,没有直接替代的功能。迁移方法:为对应的领域模型创建 AFTER_CREATE / AFTER_UPDATE / AFTER_DELETE 类型的对象钩子,在钩子的动态逻辑里自行发送 HTTP 请求。对象钩子的用法见 对象钩子。
提示
平台没有提供把旧集成定义转换为 Webhook 的迁移脚本。升级前请把旧的传入集成逐个在新版中重建为 Webhook,并把新的调用地址通知给调用方。