Webhook 用于在整个应用程序生态系统中更出色地集成 UiPath 自动化。您可以订阅 Orchestrator 事件并将其发送到任何外部 DCM、BPM 或 CRM 解决方案,同时通知不同的用户存在可供处理的新队列项目、触发器失败或已更新流程等事项。
Webhooks allow external systems to subscribe and listen to different types of Orchestrator events. The Webhooks page enables you to easily set them up and view the ones that have been previously created. You can also disable webhooks, search for a specific one, edit or delete them.
事件适用于作业、机器人、队列、队列项目、流程和计划。
每个事件都会向指定的 URL 发送包含信息的有效负载。一些属性是所有事件的通用属性,而另一些属性则特定于每种事件类型。
Webhook 事件按文件夹创建,因此,如果您有一个 Webhook 事件与在文件夹之间共享的资源(例如队列)相关联,则系统将为每个文件夹生成一个单独的 Webhook 事件。
如果转发事件的请求失败,则该特定 Webhook 的断开器会打开,从而禁用 Webhook 一小时。
- 系统将跳过本应在断开器打开时发送的任何 Webhook 事件,并且在开关关闭后不会重试该事件。
- 系统不会存储 Webhook 事件,因此无法重试或导出 Webhook 事件。此外,如果对外部平台的调用失败,则事件将丢失。Webhook 专为实时处理而设计。
通用有效负载属性
| 属性名称 | 属性类型 | 说明和示例 |
|---|---|---|
| 类型 | 字符串 | 触发通知的事件类型。 对于所有类型的事件,都会显示此属性。 示例: "Type": "job.created""Type":"process.updated" |
| 活动 ID | 字符串 | 发生时为每个事件生成的唯一标识符。 对于所有类型的事件,都会显示此属性。 示例: "EventId":"3e5af0113e674ae597c579cb35ed8630" |
| 时间戳 | RFC 8601 日期 | 生成事件的日期和时间。 对于所有类型的事件,都会显示此属性。 示例: "Timestamp":"2018-11-02T11:47:48.5790797Z" |
| 租户 ID | 整数 | 在其中生成事件的租户的 ID。默认租户为 1。 对于所有类型的事件,都会显示此属性。 示例: "TenantId":3 |
| 用户 ID | 整数 | 操作触发事件的用户 ID。 如果事件是由机器人或触发器触发的,则不会显示此参数。 对于所有类型的事件,都会显示此属性。 示例: "UserId": 4947 |
| 文件夹 ID | 整数 | 生成事件的文件夹的 ID。 对于由新式机器人触发的事件,不会显示此参数。 示例: "FolderId": 26 |
权限
要在“Webhook”页面上执行各种操作,需要获得对 Webhook 的相应权限:
- 查看 - 使您能够查看 Webhook 及其详细信息,以及使用 API 对其进行检索,发送 ping 请求或获取 Webhook 可以订阅的所有事件的列表。
- “创建”- 此权限使您可以添加新的 Webhook。请注意,您还需要“查看”权限。
- 编辑- 使您能够从用户界面或使用 API编辑 Webhook。请注意,您还需要查看权限。
- “删除”- 此权限使您可以删除 Webhook。请注意,您还需要“查看”权限。
身份验证
所有 Webhook HTTP 请求都使用您在创建 Webhook 时添加的密码进行身份验证。它通过 HMAC-SHA256 键控哈希与请求正文结合在一起。这将产生一个不包含任何密码信息的安全签名,该签名对于每个请求都是唯一的,并通过 X-UiPath-Signature HTTP 标头发送。
接收 Orchestrator 请求的客户端应用程序必须检查请求的真实性。请求签名遵循以下模式:
- 客户端应用程序收到 Orchestrator 发出的 Webhook 请求;
- 客户端应用程序根据请求计算签名;
- 客户端应用程序尝试将其计算的签名与请求签名进行匹配:
- 如果签名不匹配,则客户端应用程序不应处理请求。
- 如果签名匹配,则客户端应用程序应处理请求。
签名计算应按以下步骤完成:
-
检索
X-UiPath-SignatureHTTP 标头。 -
要获取原始签名字节,请从 Base64 解码标头的值。
-
检索原始请求正文。
备注:Orchestrator 请求始终使用 UTF-8 编码。
-
使用 SHA256 和签名密钥(UTF-8 编码)计算哈希值。
-
将计算得出的签名与
X-UiPath-SignatureHTTP 标头中的值进行比较:- 如果签名不匹配,则不会处理请求。
- 如果签名匹配,则客户端应用程序应处理请求。
签名验证示例
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
public async Task<bool> IsValidRequestAsync(HttpRequestMessage request, string secret)
{
if (!request.Headers.TryGetValues("X-UiPath-Signature", out var headerValues))
return false;
var orchestratorSignature = Convert.FromBase64String(headerValues.First());
using (var sha = new HMACSHA256(key: Encoding.UTF8.GetBytes(secret)))
{
var computedSignature = sha.ComputeHash(await request.Content.ReadAsByteArrayAsync());
return ByteArrayEquals(orchestratorSignature, computedSignature);
}
}
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
public async Task<bool> IsValidRequestAsync(HttpRequestMessage request, string secret)
{
if (!request.Headers.TryGetValues("X-UiPath-Signature", out var headerValues))
return false;
var orchestratorSignature = Convert.FromBase64String(headerValues.First());
using (var sha = new HMACSHA256(key: Encoding.UTF8.GetBytes(secret)))
{
var computedSignature = sha.ComputeHash(await request.Content.ReadAsByteArrayAsync());
return ByteArrayEquals(orchestratorSignature, computedSignature);
}
}
const { createServer } = require('http');
const { createHmac } = require('crypto');
const PORT = 9090
const WEBHOOK_SECRET = '<same secret as configured in Orchestrator>'
const isValidRequest = (body /* Buffer */, secret /* string */, expectedSignature /* string */) =>
expectedSignature == null || createHmac('sha256', secret)
.update(body)
.digest('base64') === expectedSignature
const server = createServer((req, resp) => {
let body = new Buffer([])
req.on('data', chunk => body = Buffer.concat([body, chunk]))
req.on('end', () => {
if (!isValidRequest(body, WEBHOOK_SECRET, req.headers['x-uipath-signature'])) {
console.error('Invalid signature')
resp.statusCode = 401 // Unauthorized
} else {
let payload = JSON.parse(body.toString('utf8'))
// Process request
console.log(payload)
resp.statusCode = 202 // Accepted
}
resp.end()
})
})
server.listen(PORT)
const { createServer } = require('http');
const { createHmac } = require('crypto');
const PORT = 9090
const WEBHOOK_SECRET = '<same secret as configured in Orchestrator>'
const isValidRequest = (body /* Buffer */, secret /* string */, expectedSignature /* string */) =>
expectedSignature == null || createHmac('sha256', secret)
.update(body)
.digest('base64') === expectedSignature
const server = createServer((req, resp) => {
let body = new Buffer([])
req.on('data', chunk => body = Buffer.concat([body, chunk]))
req.on('end', () => {
if (!isValidRequest(body, WEBHOOK_SECRET, req.headers['x-uipath-signature'])) {
console.error('Invalid signature')
resp.statusCode = 401 // Unauthorized
} else {
let payload = JSON.parse(body.toString('utf8'))
// Process request
console.log(payload)
resp.statusCode = 202 // Accepted
}
resp.end()
})
})
server.listen(PORT)