# Webhook 使用说明
Webhook 提供了一种标准化的方式,使客户可以订阅系统中的某些关键节点,并进行相应的业务处理。在 FinClip 管理系统中,开发者可以通过 Webhook 功能与自有的第三方系统(如短信、邮件发送系统)进行对接,以便提升用户使用体验。
事件列表如下:
事件名称 | 事件Code | 备注 |
---|---|---|
短信 | EVENT_SMS | |
邮件 | EVENT_EMAIL | |
添加小程序 | EVENT_MINIAPP_ADD | |
删除小程序 | EVENT_MINIAPP_DELETE | |
编辑小程序 | EVENT_MINIAPP_EDIT | |
发布小程序 | EVENT_MINIAPP_PUBLISH | |
取消发布小程序 | EVENT_MINIAPP_UNPUBLISH | |
回滚小程序 | EVENT_MINIAPP_ROLLBACK | |
禁用小程序 | EVENT_MINIAPP_DISABLE | |
启用小程序 | EVENT_MINIAPP_ENABLE | |
启用小程序搜索 | EVENT_MINIAPP_ENABLE_SEARCH | |
禁用小程序搜索 | EVENT_MINIAPP_DISABLE_SEARCH |
# 事件说明
# 1. 事件-短信
注意:这个地方勾选了短信的事件之后,会禁用系统原本的短信发送渠道,短信的发送功能将完全由Webhook的接收方完成
# 1.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 1.2 请求结构
{
"event": "EVENT_SMS",
"params": { // 这一层的params 根据不同的event, 会有不同的值
"scene": 100, // 短信发送场景
"areaCode": "+86", // 国家代号
"phone": "18562310708", // 手机号
"params": [ // 这一层的params 根据不同的scene, 会有不同的值
// 这里给的是object array的元数据,不会直接给短信内容。方便根据不同渠道进行不同的处理。
// 因为类似腾讯云这种短信发送渠道,是在控制台里面维护模板,然后调用的时候使用templateId调用
{
"key": "VerifyCode", // 验证码
"value": "654782"
},
{
"key": "ValidityMinutes", // 验证码有效期的分钟数,默认都是10分钟
"value": "10"
}
]
}
}
# 1.3 参数说明
参数 | 类型 | 说明 |
---|---|---|
scene | Number | 目前只会有短信验证码一种场景,即: 100: 短信验证码 |
areaCode | String | 国家代号 |
phone | String | 手机号 |
params | object array | 这里根据不同的 scene ,会有不同的 params |
# 2. 事件-邮件
注意:这个地方勾选了邮件的事件之后,会禁用系统原本的邮件发送渠道,邮件的发送功能将完全由Webhook的接收方完成
# 2.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 2.2 请求结构
{
"event": "EVENT_EMAIL",
"params": { // 这一层的params 根据不同的event, 会有不同的值
"scene": 100, // 邮件发送场景
"to": "test@gmail.com", // 邮件接收者,目前没有群发场景
"subject": "", // 邮件发送的subject
"params": [ // 这一层的params 根据不同的scene, 会有不同的值
// 这里给的是object array的元数据,不会直接给邮件内容。方便根据不同渠道进行不同的处理。
// 因为部分邮件发送渠道,是在控制台里面维护模板,然后调用的时候使用templateId调用
{
"key": "UserName", // FinClip 用户名
"value": "autolfymcp"
},
{
"key": "VerifyCode", // 邮件验证码
"value": "177793"
}
]
}
}
# 2.3 参数说明
参数 | 类型 | 说明 |
---|---|---|
scene | Number | 目前只会有邮件验证码一种场景,即: 100: 邮件验证码 |
to | String | 邮件接收者 |
subject | String | 手机号 |
params | object array | 这里根不同的 scene ,会有不同的 params |
# 3. 事件-添加小程序
# 2.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 2.2 请求结构
{
"event": "EVENT_MINIAPP_ADD",
"params": {
"appClassKey": "jinrong1", // 小程序分类key
"createBy": "2367587140282053", // 创建人
"createTime": 1728556977557, // 创建时间
"desc": "描述", // 小程序描述
"detailDesc": "详情描述", // 小程序详情描述
"isForbidden": false, // 是否禁用
"logo": "2367587140282053", // 小程序logo id
"miniAppId": "fc2398954709929221", // 小程序id
"name": "名称", // 小程序名称
"organId": "2367587140839109", // 组织id
"privacySettingType": 0, // 隐私设置类型
"projectType": 1, // 项目类型
"searchable": true, // 是否可搜索
"status": 1, // 状态
"updateTime": 1728556984213 // 更新时间
}
}
# 4. 事件-删除小程序
# 4.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 4.2 请求结构
{
"event": "EVENT_MINIAPP_DELETE",
"params": {
"appClassKey": "jinrong1", // 小程序分类key
"createBy": "2367587140282053", // 创建人
"createTime": 1728556977557, // 创建时间
"desc": "www", // 小程序描述
"detailDesc": "wwww", // 小程序详情描述
"isForbidden": false, // 是否禁用
"logo": "2356288317085829", // 小程序logo id
"miniAppId": "fc2398954709929221", // 小程序id
"name": "wwww1", // 小程序名称
"organId": "2367587140839109", // 组织id
"privacySettingType": 0, // 隐私设置类型
"projectType": 1, // 项目类型
"searchable": true, // 是否可搜索
"status": 1, // 状态
"updateTime": 1728557000325 // 更新时间
}
}
# 5. 事件-编辑小程序
# 5.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 5.2 请求结构
{
"event": "EVENT_MINIAPP_EDIT",
"params": {
"appClassKey": "jinrong1", // 小程序分类key
"createBy": "2367587140282053", // 创建人
"createTime": 1728556977557, // 创建时间
"desc": "www", // 小程序描述
"detailDesc": "wwww", // 小程序详情描述
"isForbidden": false, // 是否禁用
"logo": "2356288317085829", // 小程序logo id
"miniAppId": "fc2398954709929221", // 小程序id
"name": "wwww1", // 小程序名称
"organId": "2367587140839109", // 组织id
"privacySettingType": 0, // 隐私设置类型
"projectType": 1, // 项目类型
"searchable": true, // 是否可搜索
"status": 1, // 状态
"updateTime": 1728557000325 // 更新时间
}
}
# 6. 事件-发布小程序
# 6.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 6.2 请求结构
{
"event": "EVENT_MINIAPP_PUBLISH",
"params": {
"appClassKey": "qita", // 小程序分类key
"autoPub": false, // 是否自动发布
"createTime": 1726642520426, // 创建时间
"desc": "testAppletDescUpdate2", // 小程序描述
"detailDesc": "testAppletDetailDescUpdate2", // 小程序详情描述
"logo": "https://www-cdn.finclip.com/images/ic-default.png", // 小程序logo
"miniAppCompiledPackageId": "2367587980306374", // 小程序编译包ID
"miniAppId": "fc2367587200788357", // 小程序id
"miniAppVersionId": "2367588244187077", // 小程序版本ID
"name": "autobplgnf2", // 小程序名称
"organId": "2367587140839109", // 组织id
"sequence": 23, // 序列号
"status": 6, // 状态
"updateTime": 1728557033406, // 更新时间
"version": "1.0.19", // 版本号
"versionDescription": "版本描述" // 版本描述
}
}
# 6.3 参数说明
参数 | 类型 | 说明 |
---|---|---|
autoPub | Boolean | 是否自动发布 |
miniAppCompiledPackageId | String | 小程序编译包ID |
miniAppVersionId | String | 小程序版本ID |
sequence | Number | 序列号 |
version | String | 版本号 |
versionDescription | String | 版本描述 |
# 7. 事件-取消发布小程序
# 7.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 7.2 请求结构
{
"event": "EVENT_MINIAPP_UNPUBLISH",
"params": {
"appClassKey": "qita", // 小程序分类key
"autoPub": true, // 是否自动发布
"createTime": 1726642519646, // 创建时间
"desc": "testAppletDescUpdate2", // 小程序描述
"detailDesc": "testAppletDetailDescUpdate2", // 小程序详情描述
"logo": "https://www-cdn.finclip.com/images/ic-default.png", // 小程序logo
"miniAppCompiledPackageId": "2367588230064070", // 小程序编译包ID
"miniAppId": "fc2367587200788357", // 小程序id
"miniAppVersionId": "2367588231210949", // 小程序版本ID
"name": "autobplgnf2", // 小程序名称
"organId": "2367587140839109", // 组织id
"sequence": 22, // 序列号
"status": 7, // 状态
"updateTime": 1728557044812, // 更新时间
"version": "1.0.22", // 版本号
"versionDescription": "1.0.22" // 版本描述
}
}
# 8. 事件-回滚小程序
# 8.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 8.2 请求结构
{
"event": "EVENT_MINIAPP_ROLLBACK",
"params": {
"appClassKey": "qita", // 小程序分类key
"autoPub": true, // 是否自动发布
"createTime": 1726642519646, // 创建时间
"desc": "testAppletDescUpdate2", // 小程序描述
"detailDesc": "testAppletDetailDescUpdate2", // 小程序详情描述
"logo": "https://www-cdn.finclip.com/images/ic-default.png", // 小程序logo
"miniAppCompiledPackageId": "2367588230064070", // 小程序编译包ID
"miniAppId": "fc2367587200788357", // 小程序id
"miniAppVersionId": "2367588231210949", // 小程序版本ID
"name": "autobplgnf2", // 小程序名称
"organId": "2367587140839109", // 组织id
"sequence": 22, // 序列号
"status": 6, // 状态
"updateTime": 1728557039793, // 更新时间
"version": "1.0.22", // 版本号
"versionDescription": "1.0.22" // 版本描述
}
}
# 9. 事件-禁用小程序
# 9.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 9.2 请求结构
{
"event": "EVENT_MINIAPP_DISABLE",
"params": {
"appClassKey": "qita", // 小程序分类key
"createBy": "2367587140282053", // 创建人
"createTime": 1726642456734, // 创建时间
"desc": "testAppletDescUpdate2", // 小程序描述
"detailDesc": "testAppletDetailDescUpdate2", // 小程序详情描述
"isForbidden": true, // 是否禁用
"logo": "https://www-cdn.finclip.com/images/ic-default.png", // 小程序logo
"miniAppId": "fc2367587200788357", // 小程序id
"name": "autobplgnf2", // 小程序名称
"organId": "2367587140839109", // 组织id
"privacySettingType": 1, // 隐私设置类型
"projectType": 1, // 项目类型
"searchable": true, // 是否可搜索
"status": 7, // 状态
"updateTime": 1728557044812 // 更新时间
}
}
# 10. 事件-启用小程序
# 10.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 10.2 请求结构
{
"event": "EVENT_MINIAPP_ENABLE",
"params": {
"appClassKey": "qita", // 小程序分类key
"createBy": "2367587140282053", // 创建人
"createTime": 1726642456734, // 创建时间
"desc": "testAppletDescUpdate2", // 小程序描述
"detailDesc": "testAppletDetailDescUpdate2", // 小程序详情描述
"isForbidden": false, // 是否禁用
"logo": "https://www-cdn.finclip.com/images/ic-default.png", // 小程序logo
"miniAppId": "fc2367587200788357", // 小程序id
"name": "autobplgnf2", // 小程序名称
"organId": "2367587140839109", // 组织id
"privacySettingType": 1, // 隐私设置类型
"projectType": 1, // 项目类型
"searchable": true, // 是否可搜索
"status": 7, // 状态
"updateTime": 1728557044812 // 更新时间
}
}
# 11. 事件-启用小程序搜索
# 11.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 11.2 请求结构
{
"event": "EVENT_MINIAPP_ENABLE_SEARCH",
"params": {
"appClassKey": "qita", // 小程序分类key
"createBy": "2367587140282053", // 创建人
"createTime": 1726642456734, // 创建时间
"desc": "testAppletDescUpdate2", // 小程序描述
"detailDesc": "testAppletDetailDescUpdate2", // 小程序详情描述
"isForbidden": false, // 是否禁用
"logo": "https://www-cdn.finclip.com/images/ic-default.png", // 小程序logo
"miniAppId": "fc2367587200788357", // 小程序id
"name": "autobplgnf2", // 小程序名称
"organId": "2367587140839109", // 组织id
"privacySettingType": 1, // 隐私设置类型
"projectType": 1, // 项目类型
"searchable": true, // 是否可搜索
"status": 7, // 状态
"updateTime": 1728557073899 // 更新时间
}
}
# 12. 事件-禁用小程序搜索
# 12.1 请求头部
Header | 描述 |
---|---|
X-Fc-Webhook-Sign | (可选) 如果在管理后台勾选了Token校验的话会传。具体作用查看下面签名章节 |
# 12.2 请求结构
{
"event": "EVENT_MINIAPP_DISABLE_SEARCH",
"params": {
"appClassKey": "qita", // 小程序分类key
"createBy": "2367587140282053", // 创建人
"createTime": 1726642456734, // 创建时间
"desc": "testAppletDescUpdate2", // 小程序描述
"detailDesc": "testAppletDetailDescUpdate2", // 小程序详情描述
"isForbidden": false, // 是否禁用
"logo": "https://www-cdn.finclip.com/images/ic-default.png", // 小程序logo
"miniAppId": "fc2367587200788357", // 小程序id
"name": "autobplgnf2", // 小程序名称
"organId": "2367587140839109", // 组织id
"privacySettingType": 1, // 隐私设置类型
"projectType": 1, // 项目类型
"searchable": false, // 是否可搜索
"status": 7, // 状态
"updateTime": 1728557070073 // 更新时间
}
}
# 签名
为了使得您的服务 (webhook事件的接收方) 有途径校验请求来自可信任的调用方,提供了签名的方式。为此您需要:
- 在配置Webhook的时候,勾选生成Token
- 将生成的Token,保存起来。注意:此Token只会展示一次,妥善保存,遗失的话只能重置
- Webhook接收方中校验:Token的值+请求的入参body 计算出来的值需要等于 header中的
X-Fc-Webhook-Sign
- 配置Webhook的时候,如果没有勾选生成Token, 不会传
X-Fc-Webhook-Sign
的header
- 配置Webhook的时候,如果没有勾选生成Token, 不会传
# 签名方式说明
X-Fc-Webhook-Sign
的签名使用了请求body的 HMAC 十六进制摘要,并使用 SHA-256 哈希函数生成,并将 作为webhookToken
HMAC key
验证 webhook 有效负载时需要牢记以下几件重要的事情:
- FinClip 使用 HMAC 十六进制摘要来计算哈希值。
- 哈希签名始终以 开头
sha256=
。 - 哈希签名是使用您的 webhook 的 webhookToken 和 webhook请求体内容 生成的。
- 如果您的语言和服务器实现指定了字符编码,请确保将负载处理为 UTF-8。Webhook 负载可以包含 Unicode 字符。
# 签名示例
# Golang
您可以定义以下VerifySignature
函数并在收到 webhook 请求时调用它:
// 参数说明
// payload: webhook请求的request body
// webHookToken: 生成的webhookToken
// signature: webhook请求中header中的 X-Fc-Webhook-Sign
func VerifySignature(payload []byte, webHookToken string, signature string) bool {
expectedSignature := computeWebHookSign(payload, webHookToken)
return hmac.Equal([]byte(expectedSignature), []byte(signature))
}
func computeWebHookSign(payload []byte, webhookToken string) string {
mac := hmac.New(sha256.New, []byte(webhookToken))
mac.Write(payload)
return "sha256=" + hex.EncodeToString(mac.Sum(nil))
}
# Python
您可以定义以下verify_signature
函数并在收到 webhook 请求时调用它:
import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
"""Verify that the payload was sent from GitHub by validating SHA256.
Raise and return 403 if not authorized.
Args:
payload_body: original request body to verify (request.body())
secret_token: GitHub app webhook token (WEBHOOK_SECRET)
signature_header: header received from GitHub (x-hub-signature-256)
"""
if not signature_header:
raise HTTPException(status_code=403, detail="x-hub-signature-256 header is missing!")
hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
expected_signature = "sha256=" + hash_object.hexdigest()
if not hmac.compare_digest(expected_signature, signature_header):
raise HTTPException(status_code=403, detail="Request signatures didn't match!")
# JavaScript
let encoder = new TextEncoder();
async function verifySignature(secret, header, payload) {
let parts = header.split("=");
let sigHex = parts[1];
let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };
let keyBytes = encoder.encode(secret);
let extractable = false;
let key = await crypto.subtle.importKey(
"raw",
keyBytes,
algorithm,
extractable,
[ "sign", "verify" ],
);
let sigBytes = hexToBytes(sigHex);
let dataBytes = encoder.encode(payload);
let equal = await crypto.subtle.verify(
algorithm.name,
key,
sigBytes,
dataBytes,
);
return equal;
}
function hexToBytes(hex) {
let len = hex.length / 2;
let bytes = new Uint8Array(len);
let index = 0;
for (let i = 0; i < hex.length; i += 2) {
let c = hex.slice(i, i + 2);
let b = parseInt(c, 16);
bytes[index] = b;
index += 1;
}
return bytes;
}