指南

Deskgram 2 Webhook API:方法、JSON 与签名

本地 REST API 参考:端点、事件、字段映射、历史、重试、HMAC 与 MCP 的区别。

settings9 分钟2026-10-24

核心要点

  • 先创建 未启用 的端点,检查设置:
  • 从响应读取 endpoint.id 。假设 ID 为 12 ,发送测试:
  • 查看 success 、 delivery.response status code 、 delivery.response body ,并在接收服务检查实际请求。测试没有真实线索,可能不满足 CRM 的业务校验。
  • 在界面启用端点,或通过 PUT 提交带有 enabled: true 的 完整设置 。不要只发送 enabled,PUT 不是局部更新。

Webhook 模块概览

本文根据 2026 年 10 月 24 日的实现核对,介绍已经实现的行为,不代表未来计划。通过界面配置请参阅用户指南。

两种请求方向

方向调用方与接收方地址用途
本地管理 API本地脚本 → Deskgramhttp://127.0.0.1:5000/api/webhooks管理接收端点、查看投递历史
出站 WebhookDeskgram → 您的接收服务端点设置中的 URL通过 POST 发送 JSON 事件

127.0.0.1:5000 只是本地 backend 地址示例,请使用实际安装的地址和端口。它不是许可证云服务 API,也不是 MCP 服务器;同样不是 Telegram Bot API 的 setWebhook 或 getUpdates。

下列 REST 路由没有单独的 Bearer token 校验,适用于受信任的本地环境。不要为了接收出站通知而向互联网开放 backend 端口。出站投递不需要外部主动连接运行 Deskgram 的电脑。如果您另设了身份验证网关,其规则需要单独配置。

本地响应采用 JSON。通过 POST/PUT 提交设置时,发送 Content-Type: application/json。

API 快速开始

下面的 curl 示例适用于支持单引号的 shell。在 PowerShell 中请使用 curl.exe,并遵循当前版本处理 JSON 参数的规则。所有域名、ID 和密钥均为虚构示例。JSON 中的俄文字符串只是示例值,不是固定要求。

  1. 先创建未启用的端点,检查设置:
curl -X POST 'http://127.0.0.1:5000/api/webhooks/endpoints' \  -H 'Content-Type: application/json' \  --data '{"name":"CRM: рассылка","url":"https://receiver.example.com/deskgram","secret":"YOUR_SHARED_SECRET","events":["message.sent","message.failed","lead.reply_received"],"module_filter":["spam_pm"],"payload_template":"deskgram","enabled":false}'
  1. 从响应读取 endpoint.id。假设 ID 为 12,发送测试:
curl -X POST 'http://127.0.0.1:5000/api/webhooks/endpoints/12/test'
  1. 查看 success、delivery.responsestatuscode、delivery.response_body,并在接收服务检查实际请求。测试没有真实线索,可能不满足 CRM 的业务校验。
  2. 在界面启用端点,或通过 PUT 提交带有 enabled: true 的完整设置。不要只发送 enabled,PUT 不是局部更新。
  3. 执行一次与订阅事件对应的实际操作,再查看历史:
curl 'http://127.0.0.1:5000/api/webhooks/deliveries?endpoint_id=12&limit=20'

Endpoint 对象

创建与更新的请求字段

POST 和 PUT 使用同一个请求模型。

字段类型默认值规则
namestring必填去除首尾空格前为 1–120 个字符;请使用非空名称
urlstring必填8–2000 个字符,以 http:// 或 https:// 开头;建议 HTTPS
methodstringPOST仅支持 POST
secretstring 或 null创建时为 null共享密钥,首尾空格被删除;PUT 省略则保留,null 或空字符串则清除
headers字符串值 object{}接收服务所需的额外请求头,不是 Deskgram 访问凭据
eventsstring 数组["task.completed"]事件目录中的六种事件或 "*";空列表会替换为 task.completed
module_filterstring 数组[]空列表表示所有模块,例如 ["spam_pm"]
payload_templatestringdeskgramdeskgram、shortresult、crmlead、custom
payload_mappingobject{}输出字段配置,见下文
enabledbooleantrue是否自动投递匹配事件
timeout_secondsnumber153–60 秒

普通端点的 timeout 用于 HTTP 客户端的网络操作,并不是整个本地 API 调用的严格总时长。test 和 retry 会等待投递完成,包括自动尝试及其间隔。

模块名转为小写,- 替换为 _;事件和模块列表会去重。"*" 指所有实际进入此机制的事件,并不会给尚未接入事件的模块增加功能。

响应字段

Endpoint 包含上述设置,以及整数 id、布尔 hassecret、UTC 时间 createdat 和 updated_at。即使已保存密钥,secret 也始终返回 null。

本地 REST API 不会隐藏 headers 中的值,其中可能有接收服务的 token。不要把完整响应放入公开日志或支持消息。

创建响应示例:

{  "success": true,  "endpoint": {    "id": 12,    "name": "CRM: рассылка",    "url": "https://receiver.example.com/deskgram",    "method": "POST",    "secret": null,    "has_secret": true,    "headers": {},    "events": ["message.sent", "message.failed", "lead.reply_received"],    "module_filter": ["spam_pm"],    "payload_template": "deskgram",    "payload_mapping": {},    "enabled": false,    "timeout_seconds": 15.0,    "created_at": "2026-10-15T10:00:00Z",    "updated_at": "2026-10-15T10:00:00Z"  }}

API 方法

以下路径均包含 /api。管理操作正常完成时使用 HTTP 200,创建和删除也如此。endpointid 与 deliveryid 是对应记录的整数 ID。

GET /api/webhooks/endpoints

按创建时间从新到旧返回端点,无分页参数。响应为 {"endpoints": [...]},每项是一个 endpoint 对象;空列表为 {"endpoints": []}。

POST /api/webhooks/endpoints

使用上面的模型创建端点,返回 {"success": true, "endpoint": {...}}。

如果省略 enabled,新端点会立即启用。首次配置时可明确发送 false,在完成检查后再开启自动通知。

PUT /api/webhooks/endpoints/{endpoint_id}

更新端点。必须提供 name、url 和需要保留的其余当前设置。省略字段恢复默认值,只有 secret 例外。

不要直接把读取到的 endpoint 对象原样回传:响应中的 secret: null 会在 PUT 中清除已保存的密钥。保留旧密钥时应完全省略 secret 键;明确发送 null 或空字符串表示清除。

保留密钥并启用端点的示例:

{  "name": "CRM: рассылка",  "url": "https://receiver.example.com/deskgram",  "method": "POST",  "headers": {},  "events": ["message.sent", "message.failed", "lead.reply_received"],  "module_filter": ["spam_pm"],  "payload_template": "deskgram",  "payload_mapping": {},  "enabled": true,  "timeout_seconds": 15}

响应为 {"success": true, "endpoint": {...}}。端点不存在时返回 HTTP 404。

DELETE /api/webhooks/endpoints/{endpoint_id}

删除端点,无需请求体。返回 {"success": true, "endpoint_id": 12};不存在时为 HTTP 404。

投递历史仍保留。删除不会取消已经开始的发送;端点删除后,其历史投递无法重试。

POST /api/webhooks/endpoints/{endpoint_id}/test

只向指定端点发送测试,无需请求体。本方法不接受任意自定义测试 payload。

显式测试可以发送给未启用的端点,不受事件订阅和模块筛选限制。它会使用当前 mapping,并在发送及自动重试完成后返回。

下面是标准格式的测试被接收方以 HTTP 403 拒绝时的示例:

{  "success": false,  "delivered": 0,  "delivery_id": 45,  "status": "error",  "delivery": {    "id": 45,    "endpoint_id": 12,    "event_type": "webhook.test",    "task_id": null,    "status": "error",    "attempts": 3,    "response_status_code": 403,    "response_body": "Forbidden",    "error": "HTTP 403",    "created_at": "2026-10-15T10:01:00Z",    "updated_at": "2026-10-15T10:01:07Z",    "delivered_at": null,    "request_payload": {      "schema_version": "1.0",      "event": "webhook.test",      "occurred_at": "2026-10-15T10:01:00Z",      "source": "deskgram",      "test": true,      "endpoint": {"id": 12, "name": "CRM: рассылка"}    },    "source_payload": {      "schema_version": "1.0",      "event": "webhook.test",      "occurred_at": "2026-10-15T10:01:00Z",      "source": "deskgram",      "test": true,      "endpoint": {"id": 12, "name": "CRM: рассылка"}    }  }}

此时本地 API 的 HTTP 状态仍是 200。403 属于外部接收方,保存在 delivery 中。成功时为 success: true、delivered: 1、status: "success"。

如果没有创建投递记录,delivery_id 和 delivery 可以为 null,success 为 false。端点不存在则返回本地 HTTP 404。

GET /api/webhooks/deliveries

按创建时间从新到旧列出投递。

查询参数类型默认值含义
endpoint_idinteger未指定端点 ID
event_typestring未指定完整事件名,例如 message.failed
statusstring未指定pending、retrying、success 或 error
limitinteger1001–500 条记录

响应为 {"deliveries": [...]}。每项是不包含 requestpayload、sourcepayload 的投递对象。没有 offset、游标或总记录数。

GET /api/webhooks/deliveries/{delivery_id}

读取完整投递详情,包括 requestpayload、sourcepayload。返回 {"delivery": {...}};不存在时返回 HTTP 404。

POST /api/webhooks/deliveries/{delivery_id}/retry

手动重试状态为 error 的投递,无需请求体。它创建新投递记录并等待其尝试完成。即使端点已停用,也允许显式重试。

响应包含布尔 success、整数 originaldeliveryid、字符串 status,以及完整的新 delivery。

新投递的 id 和 X-Deskgram-Delivery-Id 与原记录不同;不会覆盖原记录。接收方失败时,本地仍返回 HTTP 200,并以 success: false 表示失败。

HTTP 400 表示原记录不是 error,或本进程已在执行该记录的手动重试。HTTP 404 表示投递或端点不存在。

GET /api/webhooks/events

返回六种支持的事件及界面标签。下面保留 API 实际返回的俄文 label;文档翻译不会改变接口值。

{  "events": [    {"event": "task.completed", "label": "Задача завершена"},    {"event": "task.failed", "label": "Задача завершилась ошибкой"},    {"event": "task.cancelled", "label": "Задача остановлена"},    {"event": "lead.reply_received", "label": "Получен ответ лида"},    {"event": "message.sent", "label": "Сообщение отправлено"},    {"event": "message.failed", "label": "Ошибка отправки сообщения"}  ]}

webhook.test 由单独的测试方法发送,不在订阅目录中。"*" 可以写入设置,但目录也不返回它。

Delivery 对象

字段类型含义
idinteger本地数据库中的投递 ID
endpoint_idinteger端点 ID,即使端点后来删除仍保留
event_typestring不受输出 mapping 改名影响的事件类型
task_idstring 或 null可用时的任务 ID
statusstringpending、retrying、success 或 error
attemptsinteger已进行的自动尝试次数
responsestatuscodeinteger 或 null最后 HTTP 状态码;网络错误时可能为 null
response_bodystring 或 null截短后的最后响应;网络错误时可能为 null
errorstring 或 null诊断信息,不是 CRM 业务错误字段
createdat、updatedatstring以 Z 结尾的 ISO 8601 UTC 时间
delivered_atstring 或 null成功投递时间
request_payloadobjectmapping 后的 JSON;仅在详情/test/retry 中出现
source_payloadobjectmapping 前的原始事件;旧记录可能是 {};仅在详情/test/retry 中出现

普通投递的响应文本最多保留 2000 个字符;MCP 受保护传输还限制读取的字节数。历史不是完整 HTTP 跟踪,不保存发出请求的所有头部,也不保存每次中间尝试的完整响应。

事件与出站 JSON

以下示例对应 payload_template: "deskgram"。使用 mapping 后,外部 JSON 结构会变化,但 X-Deskgram-Event 仍是事件名称。

任务最终事件

事件含义
task.completed任务执行完成,不保证其中每个动作均成功
task.failed任务最终错误状态
task.cancelled任务停止或取消
{  "schema_version": "1.0",  "event": "task.completed",  "occurred_at": "2026-10-15T10:05:00Z",  "source": "deskgram",  "task": {    "task_id": "12345",    "task_name": "Тестовая рассылка",    "module_name": "spam_pm",    "status": "completed",    "created_at": "2026-10-15T10:04:00Z",    "completed_at": "2026-10-15T10:05:00Z",    "accounts_count": 1,    "params": {},    "statistics": {},    "result": {}  }}

params、statistics、result 的内容取决于模块,没有适用于所有任务的统一计数器模式。taskname、modulename、created_at 可以为 null。failed/cancelled 使用同类结构,事件、状态和可用结果相应变化。

实时消息事件

目前 message.sent、message.failed、lead.replyreceived 接入的是 spampm。把筛选器换成另一个模块不会产生新的事件来源。

已发送消息的示例:

{  "schema_version": "1.0",  "event": "message.sent",  "occurred_at": "2026-10-15T10:04:20Z",  "source": "deskgram",  "module": "spam_pm",  "task": {    "task_id": "12345",    "campaign_id": "spam_pm:12345",    "module_name": "spam_pm"  },  "account": {"id": "987654321"},  "lead": {    "telegram_user_id": "123456789",    "username": "example_client",    "raw_identifier": "@example_client"  },  "message": {    "message_id": 1001,    "message_text": "Здравствуйте!",    "raw_identifier": "@example_client",    "text": "Здравствуйте!",    "status": "sent"  }}

lead 和 message 的字段取决于可用数据。电话、username、姓名、ID 或文本都可能缺失。映射文本时用 message.text。不要把 message_text 作为通用必填项:它只是特定发送路径提供的额外字段。

message.failed 的 message 中包含可用的 error 和状态,不一定有成功发送的 message_id。跳过收件人或解析收件人失败,并不一定生成此事件。

lead.replyreceived 的 message 可包含 messageid、text、media_type、datetime、timestamp、status: "received"。只有正在运行的处理器实际收到回复时才产生事件。Webhook 本身不会开启全天账号监听,也不保证任务结束后仍收集回复。

message.sent 不表示收件人已读。建议把 ID 视为不透明标识;此契约中的任务、账号和 Telegram 用户 ID 是字符串,个别 message_id 则可能是数值。

测试事件

原始测试包含 schemaversion、event: "webhook.test"、occurredat、source、test: true、endpoint: {id, name}。完整结构可见 test 方法响应。

测试中没有 task、lead、account、message。只映射线索字段时会得到 {}。

字段映射

deskgram 发送完整原始事件,忽略 payload_mapping。需要限制传输数据时请选择其他模板。

对于 shortresult、crmlead、custom,推荐明确使用:

{  "fields": [    {"target": "event", "source": "event", "enabled": true},    {"target": "contact.telegram_id", "source": "lead.telegram_user_id", "enabled": true},    {"target": "contact.username", "source": "lead.username", "enabled": true},    {"target": "comment", "source": "message.text", "enabled": true},    {"target": "status", "source": ["message.status", "task.status"], "enabled": true}  ]}

把整个对象放进 payload_mapping。target 是接收方字段路径,source 是 Deskgram 原始事件中的路径,不是常量或可执行表达式。

上述 message.sent 示例的映射结果:

{  "event": "message.sent",  "contact": {"telegram_id": "123456789", "username": "example_client"},  "comment": "Здравствуйте!",  "status": "sent"}

规则如下:

  • target 中的点创建嵌套对象,不要同时设置 contact 和 contact.username 之类冲突路径。
  • source 按点读取嵌套对象键,不支持数组下标或 {{...}} 模板。
  • 缺失值或 null 被省略;单一来源的 0、false、空字符串保留原 JSON 类型。
  • 来源列表采用第一个不是 null、空字符串、空列表、空对象的值;也支持 "message.status|task.status"。
  • enabled: false 排除该字段,省略 enabled 表示启用。
  • {"fields": []} 或全部禁用产生 {},不会替换为完整 payload。
  • payloadmapping 为 {} 时,shortresult/crm_lead 使用内置字段,custom 则产生 {}。显式 fields 可固定输出契约。

内置 shortresult 使用可用的 event、occurredat、taskid、module、status、lead、message、error。内置 crmlead 包含 event、occurredat、taskid、accountid、telegramuserid、username、phone、firstname、lastname、rawidentifier、messageid、messagetext、messagestatus、messageerror。

界面可能为预设保存自己的可编辑 fields 列表。端点实际保存的设置决定输出,不应仅凭预设名称推断全部结构。

出站请求头

请求头值
Content-Typeapplication/json
User-AgentDeskgram-Webhooks/1.0
X-Deskgram-Delivery-Id投递记录 ID 的字符串形式
X-Deskgram-Event事件名,例如 message.sent
x-secret已配置的共享密钥
X-Deskgram-Webhook-Secret同一共享密钥
X-Deskgram-Signature配置密钥时的 sha256= 签名

密钥不只用于 HMAC,还会作为两个头部的值发送。请使用 HTTPS,并避免将这些头部写入公开日志。

无论大小写如何,headers 都不能覆盖系统 Content-Type、User-Agent、Delivery-Id、Event;配置密钥后也不能覆盖三个密钥相关头部。接收方 Authorization 等其他头部可以另外添加。

验证签名

算法为 HMAC-SHA256(secretutf8, rawrequest_body)。头部值由 sha256= 加小写十六进制摘要组成。

签名针对实际发送的准确字节。JSON 使用 UTF-8,不对非 ASCII 字符作 ASCII 转义,键排序且分隔符不带空格。接收方必须使用原始 HTTP body,不要先解析 JSON 再序列化。

以下 Python 示例只使用标准库:

import hashlibimport hmacdef verify_deskgram_signature(raw_body: bytes, signature: str, secret: str) -> bool:    if not secret or not signature:        return False    expected = "sha256=" + hmac.new(        secret.encode("utf-8"), raw_body, hashlib.sha256    ).hexdigest()    try:        return hmac.compare_digest(expected, signature)    except TypeError:        return False

传入原始 body 字节、X-Deskgram-Signature 头部和接收方保存的密钥。验证失败时不要执行业务动作。密钥应从受保护配置加载,不能直接信任传入的 x-secret。

签名本身不能防止重放已签名请求;时间检查和去重需要接收方逻辑。Delivery-Id 不在签名 body 内。密钥会去掉首尾空格,建议使用不带首尾空格、长度不超过 1000 的非空 ASCII 密钥。

重试与保证范围

  • 只有 HTTP 200–299 为成功。不会跟随 3xx。HTTP 200 中即使带业务错误 JSON,仍视为传输成功。
  • 最多三次自动尝试,第二、三次前大约等待 2、4 秒;网络失败和 HTTP 拒绝(包括 4xx)都会重试,不单独处理 Retry-After。
  • 自动尝试使用相同 body 和 Delivery-Id。接收方应保证重复请求不会重复已完成的业务动作。
  • 手动 retry 使用当前 URL、secret、headers。存在 sourcepayload 时,对原始事件应用当前 mapping;旧记录缺少原事件时,直接重发已保存的 requestpayload,不二次映射。
  • 手动重试创建新 Delivery-Id。业务去重还应使用适合场景的任务/账号/消息标识和事件类型。
  • Delivery-Id 只在某个本地数据库中唯一,不是所有 Deskgram 安装的全局 ID。接收多个来源时应区分发送方。
  • 不保证 exactly-once、不同事件顺序或进程重启后自动恢复队列。中断后的 pending/retrying 不代表下次启动会继续。
  • 停用端点只阻止新的自动事件选择,不取消已开始的发送;显式 test/retry 仍可发送到停用端点。

建议接收流程:验证签名,可靠保存事件或写入自己的队列,返回 2xx,再单独执行耗时业务处理。

API 错误

本地 HTTP 状态情况
200操作完成;test/retry 还应检查 success 和 delivery
400不支持的方法/格式/事件、URL 无 http(s)、手动重试状态无效
404端点或投递不存在,或原投递的端点已删除
422请求体、参数类型、limit/timeout 范围验证失败

应用错误示例,实际服务器文本可能仍为俄文:

{"detail": "Webhook endpoint не найден"}

验证失败时 detail 可能是 FastAPI 诊断数组,而不是字符串。数据库或 backend 故障还可能造成服务器错误,这不是外部 CRM 的响应。

与 MCP 的区别

MCP 是 AI 代理的独立管理接口,并不等同于此 REST API。连接方式见 MCP 概览。

属性本地 REST APIMCP 工具
新端点默认 enabled=true默认停用
预览模式请求实际执行创建/修改按 MCP 设置默认 dry-run
更新完整 PUT,secret 保留规则例外局部更新
接收地址http(s),不要开放本地管理 API仅公共 HTTPS,使用网络保护策略
Timeout 设置3–60 秒3–30 秒
读取内容完整 URL、headers 值、payload 详情不返回密钥、headers 值或完整投递 body

通过适用的实际 MCP 操作创建或纳入管理的端点,在以后发送时仍保留网络保护规则,包括从界面发出的请求。普通 PUT 不会解除。只读、dry-run、停用或删除操作不会把旧端点转入新策略。

契约来源

维护参考:REST 模型和路由 web/routes/api/webhooks.py;事件/签名/重试 web/webhookdispatcher.py;实时数据 functions/functions/spampm/crmintegration.py;受保护传输 web/webhooknetwork.py;MCP web/mcp/webhooks.py。这些是维护人员的仓库路径,使用模块不需要读取源代码。

继续

下载 Deskgram 2

下一步

常用链接