Checked against the implementation on 24 October 2026. This reference describes implemented behavior, not planned features. For setup in the interface, see the user guide.
Two directions of requests
| Direction | Caller and destination | Address | Purpose |
|---|---|---|---|
| Local management API | Your local script → Deskgram | http://127.0.0.1:5000/api/webhooks | Configure endpoints and inspect delivery history |
| Outbound webhook | Deskgram → your receiver | Endpoint URL | Send an event in a JSON POST request |
127.0.0.1:5000 is an example local backend address. Use the address and port of your installation. This is neither the licensing server API nor the MCP server. It is also separate from Telegram Bot API methods such as setWebhook and getUpdates.
The REST routes described here have no separate Bearer-token check. Use them in a trusted local environment; do not expose the backend port to the internet to receive outbound webhooks. Delivery does not require inbound access to the computer running Deskgram. A separate authorization gateway, if installed, has its own configuration.
Local responses use JSON. Send Content-Type: application/json when submitting endpoint settings with POST or PUT.
API quick start
The following curl examples use a shell supporting single quotes. In PowerShell, use curl.exe and follow your shell version's JSON quoting rules. Domains, IDs and secrets in examples are fictional. Russian text inside example JSON is a sample value, not a required field value.
- Create a disabled endpoint to inspect its settings first:
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}'- Read
endpoint.idfrom the response. For the example ID12, send a test:
curl -X POST 'http://127.0.0.1:5000/api/webhooks/endpoints/12/test'- Inspect
success,delivery.responsestatuscode,delivery.response_bodyand the request received by your application. A test does not contain a real lead and may fail the CRM's business validation. - Enable the endpoint in the interface, or send its complete settings using PUT with
enabled: true. PUT is not a partial update. - Perform a short real action matching the subscribed event and inspect history:
curl 'http://127.0.0.1:5000/api/webhooks/deliveries?endpoint_id=12&limit=20'Endpoint object
Create and update request fields
POST and PUT use the same request model.
| Field | Type | Default | Rules |
|---|---|---|---|
name | string | Required | 1–120 characters before trimming; use a nonempty name |
url | string | Required | 8–2000 characters; starts with http:// or https://; HTTPS is recommended |
method | string | POST | Only POST is supported |
secret | string or null | null on creation | Shared secret, trimmed; omitted in PUT preserves it, null or an empty value clears it |
headers | object of string values | {} | Receiver's additional headers, not Deskgram credentials |
events | array of strings | ["task.completed"] | Six catalog events or "*"; an empty list becomes task.completed |
module_filter | array of strings | [] | Empty means all modules; example: ["spam_pm"] |
payload_template | string | deskgram | deskgram, shortresult, crmlead, custom |
payload_mapping | object | {} | Output field configuration described below |
enabled | boolean | true | Allows automatic delivery of matching events |
timeout_seconds | number | 15 | Between 3 and 60 seconds |
For ordinary endpoints, timeout applies to HTTP client's network operations, rather than a strict total duration of the local API call. Test and retry calls wait for delivery, including automatic attempts and pauses.
Module names are lowercased and - is replaced with _. Duplicate event and module entries are removed. "*" means all events that actually reach this mechanism; it does not add event integrations to modules that lack them.
Response fields
An endpoint contains these settings plus id (integer), hassecret (boolean), createdat and updated_at (UTC timestamps). secret always returns null, even when a secret is stored.
The local REST API returns the actual values in headers without masking. These may contain receiver tokens. Avoid sharing complete responses in public logs or support messages.
Example creation response:
{ "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 methods
All paths include /api. A completed management operation returns HTTP 200, including creation and deletion. endpointid and deliveryid are integer record IDs.
GET /api/webhooks/endpoints
Lists endpoints from newest to oldest by creation time, without pagination parameters. Response: {"endpoints": [...]}; each item is an endpoint object. An empty collection returns {"endpoints": []}.
POST /api/webhooks/endpoints
Creates an endpoint from the settings model. Response: {"success": true, "endpoint": {...}}.
If enabled is omitted, the new endpoint is enabled immediately. Explicitly use false for initial configuration before automatic delivery is needed.
PUT /api/webhooks/endpoints/{endpoint_id}
Updates an endpoint. Provide name, url and all current settings you want to retain. Omitted fields revert to their defaults, except secret.
Do not forward a response object unchanged: its secret: null will clear the stored secret when submitted with PUT. Remove the secret key to retain the existing value. An explicitly supplied empty string or null removes it.
Example enabling an endpoint while preserving its secret:
{ "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}Response: {"success": true, "endpoint": {...}}. A missing endpoint returns HTTP 404.
DELETE /api/webhooks/endpoints/{endpoint_id}
Deletes an endpoint. No request body is required. Response: {"success": true, "endpoint_id": 12}. A missing endpoint returns HTTP 404.
Delivery history remains. Deletion does not cancel a send that has already started. Historical deliveries cannot be retried after their endpoint is deleted.
POST /api/webhooks/endpoints/{endpoint_id}/test
Sends a test only to the specified endpoint. No request body is required, and this method does not accept an arbitrary custom test payload.
An explicit test also works for disabled endpoints, independently of event subscriptions and module filters. It applies the current mapping and returns after delivery and automatic retries finish.
Example when the receiver rejects standard-format delivery with 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: рассылка"} } }}The local API HTTP status is still 200. The 403 belongs to the external receiver and appears inside delivery. Successful delivery returns success: true, delivered: 1, status: "success".
If no delivery was created, delivery_id and delivery may be null, with success: false. A missing endpoint returns a local HTTP 404.
GET /api/webhooks/deliveries
Lists deliveries from newest to oldest by creation time.
| Query parameter | Type | Default | Meaning |
|---|---|---|---|
endpoint_id | integer | Unspecified | Endpoint ID |
event_type | string | Unspecified | Exact event name, such as message.failed |
status | string | Unspecified | pending, retrying, success or error |
limit | integer | 100 | Between 1 and 500 records |
Response: {"deliveries": [...]}. Each item is a delivery object without requestpayload and sourcepayload. The method does not provide an offset, cursor or total record count.
GET /api/webhooks/deliveries/{delivery_id}
Returns delivery details including requestpayload and sourcepayload: {"delivery": {...}}. A missing delivery returns HTTP 404.
POST /api/webhooks/deliveries/{delivery_id}/retry
Manually retries a delivery whose status is error. No body is required. This creates a new delivery record and waits for its attempts to finish. Explicit retry is allowed even if the endpoint is disabled.
The response contains success (boolean), originaldeliveryid (integer), status (string) and delivery (the full new delivery).
The new record has a different id and X-Deskgram-Delivery-Id; the original record remains unchanged. Receiver failure returns local HTTP 200 with success: false.
HTTP 400 means the source is not in error status or a manual retry for it is already running in this process. HTTP 404 means the delivery or its endpoint was not found.
GET /api/webhooks/events
Returns the six supported events with interface labels. The actual labels returned by this endpoint are shown below; translating the documentation does not change API values.
{ "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 is sent by the separate test method and is absent from the subscription catalog. "*" is accepted in settings but also is not returned by this catalog.
Delivery object
| Field | Type | Meaning |
|---|---|---|
id | integer | Delivery ID in the local database |
endpoint_id | integer | Endpoint ID, including an endpoint subsequently deleted |
event_type | string | Event name independent of output mapping |
task_id | string or null | Task ID when available |
status | string | pending, retrying, success or error |
attempts | integer | Number of automatic attempts made |
responsestatuscode | integer or null | Latest HTTP status; may be null for a network failure |
response_body | string or null | Latest receiver response, truncated; may be null for a network failure |
error | string or null | Diagnostic message, not a CRM business-error field |
createdat, updatedat | string | ISO 8601 UTC timestamps ending in Z |
delivered_at | string or null | Successful delivery time |
request_payload | object | Mapped JSON; available in details/test/retry only |
source_payload | object | Original event before mapping; may be {} in old records; details/test/retry only |
For ordinary delivery, response text is limited to 2000 characters. Protected MCP transport also limits the bytes it reads. History is not a complete HTTP trace: it does not include outgoing request headers or every intermediate attempt's response.
Events and outgoing JSON
These examples use payload_template: "deskgram". Mapping changes the external structure; X-Deskgram-Event still carries the event name.
Final task events
| Event | Meaning |
|---|---|
task.completed | Task execution finished; not proof that every action succeeded |
task.failed | Final task error status |
task.cancelled | Task stopped or 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 and result depend on the module; there is no common counter schema for every task. taskname, modulename and created_at may be null. Failed and cancelled tasks use the same structure family, with their corresponding event, status and available results.
Live messaging events
Currently message.sent, message.failed and lead.replyreceived are connected to spampm. Filtering for another module does not create new event sources.
Example sent-message event:
{ "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" }}The available lead and message fields depend on the underlying data. A phone number, username, name, ID or text may be absent. For text mapping, use message.text. Do not require message_text: it is an additional field from a particular sending path, not universal across events.
For message.failed, message contains the available error and status; a successfully sent message ID may not exist. Skipped recipients and recipient-resolution failures do not always produce this event.
For lead.replyreceived, available message fields include messageid, text, media_type, datetime, timestamp and status: "received". The event exists only when an active handler actually receives the reply. A webhook does not enable continuous account listening or guarantee replies after the task has finished.
message.sent does not confirm that the recipient read the message. Treat IDs as opaque values: task, account and Telegram user IDs are strings in this contract; a particular message_id may be numeric.
Test event
The original event contains schemaversion, event: "webhook.test", occurredat, source, test: true and endpoint: {id, name}. The test-method response above shows the complete structure.
There is no task, lead, account or message in the test. A mapping containing only lead sources produces {}.
Field mapping
deskgram sends the complete original event and ignores payload_mapping. Select another template to limit transmitted fields.
For shortresult, crmlead and custom, the recommended explicit mapping structure is:
{ "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} ]}Supply this entire object as payload_mapping. target is a field path at the receiver, while source is a path in the original Deskgram event. These are not constant values or executable expressions.
For the sent-message example, the result is:
{ "event": "message.sent", "contact": {"telegram_id": "123456789", "username": "example_client"}, "comment": "Здравствуйте!", "status": "sent"}Mapping rules:
- A dot in
targetcreates nested objects. Avoid conflicting paths such ascontactandcontact.usernametogether. sourcereads nested object keys separated by dots. Array indexes and{{...}}templates are unsupported.- An unavailable or null source is omitted. A single source preserves 0, false and the empty string with their original JSON types.
- A source list chooses the first value other than null, an empty string, empty list or empty object. The string
"message.status|task.status"is also supported. enabled: falseexcludes a field; omitted enabled means included.{"fields": []}or all fields disabled produces{}without falling back to the full event.- With
payloadmapping: {}, shortresult/crm_lead use built-in fields, while custom produces{}. Explicit fields fix the exact output contract.
Built-in shortresult uses available event, occurredat, taskid, module, status, lead, message and error. Built-in crmlead uses event, occurredat, taskid, accountid, telegramuserid, username, phone, firstname, lastname, rawidentifier, messageid, messagetext, messagestatus and messageerror.
The interface may save its own editable fields list for a preset. An endpoint's saved settings, rather than the preset name alone, determine its actual output.
Outgoing headers
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Deskgram-Webhooks/1.0 |
X-Deskgram-Delivery-Id | Delivery record ID represented as a string |
X-Deskgram-Event | Event name, for example message.sent |
x-secret | Shared secret when configured |
X-Deskgram-Webhook-Secret | The same shared secret when configured |
X-Deskgram-Signature | sha256= signature when a secret is configured |
The secret is both an HMAC key and a value sent in two headers. Use HTTPS and keep these headers out of public logs.
System Content-Type, User-Agent, Delivery-Id and Event headers cannot be overridden by headers, regardless of case. With a configured secret, the three secret-related headers are also reserved. Other headers, such as the receiver's Authorization, are added separately.
Signature verification
Algorithm: HMAC-SHA256(secretutf8, rawrequest_body). The header contains sha256= followed by a lowercase hexadecimal digest.
The signature covers the exact bytes sent. JSON is UTF-8 encoded, without ASCII escaping of non-ASCII text, with sorted keys and compact separators. At the receiver, use the raw HTTP body, not JSON parsed and serialized again.
Python verification using only the standard library:
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 FalsePass the raw body bytes, X-Deskgram-Signature header and receiver's stored secret. Do not perform business actions after a failed check. Load your secret from protected configuration rather than trusting the incoming x-secret value.
A signature alone does not prevent replay of a previously signed body. Age checks and deduplication need receiver-side logic. Delivery-Id is not included in the signed body. Secrets are trimmed; use a nonempty ASCII secret without surrounding spaces, up to 1000 characters.
Retries and delivery guarantees
- Only HTTP 200–299 counts as success. Redirects are not followed. HTTP 200 containing a business error in JSON still counts as transport success.
- Up to three automatic attempts are made, with roughly 2 and 4 seconds before the second and third. Network failures and HTTP failures, including 4xx, are retried. Retry-After is not separately processed.
- Automatic attempts use the same body and Delivery-Id. The receiver should accept a repeated request without repeating an action already completed.
- Manual retry uses the current URL, secret and headers. When sourcepayload exists, current mapping is applied to the original event. Old records without an original event reuse saved requestpayload without mapping it again.
- Manual retry creates a new Delivery-Id. Deduplicate business actions using domain identifiers as well, such as task/account/message plus event type, appropriate to the workflow.
- Delivery-Id is unique within one local database, not across all Deskgram installations. Distinguish senders when handling several installations.
- There is no exactly-once or cross-event ordering guarantee, nor automatic queue recovery after the process stops. An interrupted pending/retrying record does not imply automatic continuation on restart.
- Disabling an endpoint stops its selection for new automatic events, but does not cancel sends already started. Explicit test/retry can send to a disabled endpoint.
A practical receiver flow is: verify the signature, durably store or queue the event, return 2xx, then perform lengthy processing separately.
API errors
| Local HTTP status | Meaning |
|---|---|
| 200 | Operation completed; inspect success and delivery for test/retry |
| 400 | Unsupported method/template/event, URL without http(s), invalid manual retry |
| 404 | Missing endpoint/delivery, or source delivery's endpoint was deleted |
| 422 | Body validation, parameter type, or limit/timeout range validation failed |
Example application error; the actual server text may remain Russian:
{"detail": "Webhook endpoint не найден"}For validation failures, detail may be a FastAPI diagnostic array rather than a string. A database or backend failure can also produce a server error; this is separate from the external CRM response.
Differences from MCP
MCP is a separate management interface for an AI agent, not another name for this REST API. See the MCP overview for connection setup.
| Property | Local REST API | MCP tools |
|---|---|---|
| New endpoint | enabled=true by default | Disabled by default |
| Preview mode | Request executes | Creation/changes default to dry-run according to MCP settings |
| Update | Full PUT except secret preservation | Partial update |
| Receiver URL | http(s); keep the local API off the public internet | Public HTTPS only, protected network policy |
| Timeout setting | 3–60 seconds | 3–30 seconds |
| Reads | Full URL, header values and payload details | No secrets, header values or complete delivery bodies |
An endpoint created or taken under management by an applicable real MCP action keeps protected network rules for subsequent sends, including sends initiated in the interface. Ordinary PUT does not remove that mode. Reading, dry-run, disabling or deleting through MCP does not put an old endpoint under this policy.
Contract sources
For documentation maintenance: REST model and routes — web/routes/api/webhooks.py; events, signatures and retries — web/webhookdispatcher.py; live data — functions/functions/spampm/crmintegration.py; protected transport — web/webhooknetwork.py; MCP — web/mcp/webhooks.py. These repository paths are for maintainers; using the feature does not require access to source files.