Guide

Deskgram 2 webhook API: methods, JSON and signatures

Local REST API reference: endpoints, events, mapping, history, retries, HMAC and MCP differences.

settings17 min2026-10-24

Key Takeaways

  • Create a disabled endpoint to inspect its settings first:
  • Read endpoint.id from the response. For the example ID 12 , send a test:
  • Inspect success , delivery.response status code , delivery.response body and the request received by your application. A test does not contain a real lead and may fail the CRM& x27;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.

Webhook module overview

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

DirectionCaller and destinationAddressPurpose
Local management APIYour local script → Deskgramhttp://127.0.0.1:5000/api/webhooksConfigure endpoints and inspect delivery history
Outbound webhookDeskgram → your receiverEndpoint URLSend 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.

  1. 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}'
  1. Read endpoint.id from the response. For the example ID 12, send a test:
curl -X POST 'http://127.0.0.1:5000/api/webhooks/endpoints/12/test'
  1. Inspect success, delivery.responsestatuscode, delivery.response_body and the request received by your application. A test does not contain a real lead and may fail the CRM's business validation.
  2. Enable the endpoint in the interface, or send its complete settings using PUT with enabled: true. PUT is not a partial update.
  3. 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.

FieldTypeDefaultRules
namestringRequired1–120 characters before trimming; use a nonempty name
urlstringRequired8–2000 characters; starts with http:// or https://; HTTPS is recommended
methodstringPOSTOnly POST is supported
secretstring or nullnull on creationShared secret, trimmed; omitted in PUT preserves it, null or an empty value clears it
headersobject of string values{}Receiver's additional headers, not Deskgram credentials
eventsarray of strings["task.completed"]Six catalog events or "*"; an empty list becomes task.completed
module_filterarray of strings[]Empty means all modules; example: ["spam_pm"]
payload_templatestringdeskgramdeskgram, shortresult, crmlead, custom
payload_mappingobject{}Output field configuration described below
enabledbooleantrueAllows automatic delivery of matching events
timeout_secondsnumber15Between 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 parameterTypeDefaultMeaning
endpoint_idintegerUnspecifiedEndpoint ID
event_typestringUnspecifiedExact event name, such as message.failed
statusstringUnspecifiedpending, retrying, success or error
limitinteger100Between 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

FieldTypeMeaning
idintegerDelivery ID in the local database
endpoint_idintegerEndpoint ID, including an endpoint subsequently deleted
event_typestringEvent name independent of output mapping
task_idstring or nullTask ID when available
statusstringpending, retrying, success or error
attemptsintegerNumber of automatic attempts made
responsestatuscodeinteger or nullLatest HTTP status; may be null for a network failure
response_bodystring or nullLatest receiver response, truncated; may be null for a network failure
errorstring or nullDiagnostic message, not a CRM business-error field
createdat, updatedatstringISO 8601 UTC timestamps ending in Z
delivered_atstring or nullSuccessful delivery time
request_payloadobjectMapped JSON; available in details/test/retry only
source_payloadobjectOriginal 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

EventMeaning
task.completedTask execution finished; not proof that every action succeeded
task.failedFinal task error status
task.cancelledTask 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 target creates nested objects. Avoid conflicting paths such as contact and contact.username together.
  • source reads 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: false excludes 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

HeaderValue
Content-Typeapplication/json
User-AgentDeskgram-Webhooks/1.0
X-Deskgram-Delivery-IdDelivery record ID represented as a string
X-Deskgram-EventEvent name, for example message.sent
x-secretShared secret when configured
X-Deskgram-Webhook-SecretThe same shared secret when configured
X-Deskgram-Signaturesha256= 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 False

Pass 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 statusMeaning
200Operation completed; inspect success and delivery for test/retry
400Unsupported method/template/event, URL without http(s), invalid manual retry
404Missing endpoint/delivery, or source delivery's endpoint was deleted
422Body 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.

PropertyLocal REST APIMCP tools
New endpointenabled=true by defaultDisabled by default
Preview modeRequest executesCreation/changes default to dry-run according to MCP settings
UpdateFull PUT except secret preservationPartial update
Receiver URLhttp(s); keep the local API off the public internetPublic HTTPS only, protected network policy
Timeout setting3–60 seconds3–30 seconds
ReadsFull URL, header values and payload detailsNo 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.

Continue

Download Deskgram 2

Next step

Helpful Links