Webhooks
Application webhooks
On the Webhooks page, a developer application sends selected events to an external URL. One webhook can listen for several event types, and one application can have several webhooks with different URLs and event sets.
Use HTTPS for a production endpoint. One delivery attempt waits no more than 120 seconds, but the handler should preferably accept the event into its own queue and return 2xx promptly.
The General tab contains public application events, while For tools contains requests from HTTP tools created in the builder. Both types have request history, but waiting for an agent result applies only to tools.
Available events
command_start— the user sent/start;datacontainscommandandargs;message_allow— the user allowed messages;message_new— a new user message; its text is indata.content;lead_created— a lead was created;lead_unsubscribed— the user unsubscribed from messages;lead_blocked— the user blocked the bot;message_undelivered— a message was not delivered;error— event processing raised an error.
For subscription- and error-related events, data can contain key and error_message. Fields without a value arrive as null; do not require every common field for every event type.
Creating a webhook
On the webhooks page, open the General tab, then:
- Click Create Webhook.
- In the creation form, enter the full handler URL.
- Select one or more event types.
- Click Create.

A new webhook immediately receives Active status.
Secret and signature
After creation, a secret dialog opens. Use the copy action, store the value in a protected secret store, and only then click Done. The full secret is shown once; the list retains only its short prefix.
Every request contains these headers:
Content-Type: application/json;X-App-Id— the application's Client ID;X-Webhook-Timestamp— the event time, matchingtimestampin the body;X-Webhook-Event-Id— the unique event ID, matchingevent_idin the body;X-Webhook-Signature— a hexadecimal HMAC-SHA256 string.
The signature protects the complete request body. JSON is first canonicalized: keys in every object are sorted, array order is preserved, properties with an undefined value are omitted, and undefined inside an array becomes null. The signature version, time, event ID, and canonical JSON are then joined:
import { createHmac, timingSafeEqual } from "node:crypto";
function canonicalize(value) {
if (value === null || ["boolean", "string"].includes(typeof value)) {
return value;
}
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (Array.isArray(value)) {
return value.map((item) =>
item === undefined ? null : canonicalize(item),
);
}
if (typeof value === "object") {
return Object.fromEntries(
Object.keys(value)
.sort()
.filter((key) => value[key] !== undefined)
.map((key) => [key, canonicalize(value[key])]),
);
}
throw new TypeError("Unsupported webhook payload value");
}
const canonicalPayload = JSON.stringify(canonicalize(payload));
const signedContent = [
"v1",
payload.timestamp,
payload.event_id,
canonicalPayload,
].join(".");
const expectedHex = createHmac("sha256", webhookSecret)
.update(signedContent, "utf8")
.digest("hex");
const expected = Buffer.from(expectedHex, "hex");
const received = Buffer.from(request.headers["x-webhook-signature"] ?? "", "hex");
const signatureIsValid =
received.length === expected.length && timingSafeEqual(received, expected);
Before processing, make sure X-Webhook-Timestamp and X-Webhook-Event-Id exactly match the body fields, then validate the signature, X-App-Id, allowed project_id, event type, and a freshness window appropriate for your integration. Never write the secret to logs or send it in chat.
Request format
A production request has this common shape:
{
"event_id": "019c5a23-8b7c-7f10-a4dd-c4f4b650032a",
"event_type": "message_new",
"timestamp": "2026-07-10T12:00:00.000Z",
"project_id": "project-id",
"channel_id": "channel-id",
"channel_type": "telegram",
"lead_id": "lead-id",
"dialog_id": "dialog-id",
"platform_user_id": "platform-user-id",
"data": {
"content": "Message text"
}
}
data depends on event_type. Do not make the handler depend on fields unrelated to the selected event. Use event_id as the idempotency key: retain accepted IDs and do not execute the same event twice.
Testing and retries
Each webhook in the list has a Test action. It sends one request with its own event_id, event_type: "test", project_id: "test", null identifiers, and a message in data. Headers and the signature follow the same rules as a production event. Handle this service event separately from the production event list.
The test succeeds only for an HTTP 200-299 response and reports the status code and response time. It is one immediate request without retries and can wait up to 120 seconds for a response.
A production event is queued with a one-day retry window. After a network error, timeout, or non-2xx response, Senler makes up to 12 attempts: immediately, then approximately after 1, 5, 15, and 30 minutes, and 1, 2, 4, 8, 12, 18, and 24 hours. Every attempt preserves the same event_id, timestamp, body, and signature. Recognize retries by event_id and return 2xx when the event has already been accepted durably.
Managing and replacing a secret
A card shows selected events, status, last-triggered time, and the last HTTP status. Use the status toggle to disable and enable a webhook; a disabled webhook receives no new production events.
Open the webhook page to change its URL or public event set, then select Save. The full secret of an existing webhook is not shown again. If it is lost, safely create a new webhook, switch the handler, and only then delete the old one.

The delete webhook action opens a confirmation. After final deletion, delivery to that URL stops and the secret cannot be recovered.
Tool webhooks
The For tools tab shows the application's HTTP tools and their webhooks. The URL, parameters, and execution mode are configured in the tool builder; the webhook page provides an Edit tool action.

- in instant mode, the agent waits for one response and there are no automatic retries;
- in wait-for-result mode, the request is queued and the agent continues the step after successful delivery;
- in background mode, the request is also queued, but its response does not continue the agent's current step.
The call body contains event_type: "tool_call", event, application, installation, and project IDs, the tool's system name, and arguments. The handler must validate its input and return a result that is clear to the agent. Execution modes, timeouts, retry windows, and an exact payload example are documented in App Settings.
Delivery history and recovery
On the webhook page, the Requests area shows delivery operations that are queued, waiting for retry, completed, permanently undelivered, or removed from processing. Open an operation and then a specific attempt to inspect the source JSON, HTTP response or network error, and the time of each attempt.
After fixing the cause, use Retry. It creates a new operation with the same JSON. The external service must recognize the repeated event_id, or the action may run twice.
For a failed wait-for-result tool, Retry and continue is available. After a successful response, the result is passed to the waiting step and execution resumes. A regular retry checks delivery but does not itself resume a waiting agent. If no retry is needed, mark the problem as resolved. It disappears from the error indicator while request history remains available.

If events do not arrive
- Make sure the webhook has Active status and the required event type is selected.
- Run Test and inspect the last HTTP status and trigger time.
- Verify that the external HTTPS URL is reachable and responds within 120 seconds.
- Make sure the handler accepts
Content-Type: application/jsonand the serviceevent_type: "test". - Compare
X-App-Id,X-Webhook-Timestamp, andX-Webhook-Event-Idwith the request body. - Canonicalize the full body, build
v1.timestamp.event_id.canonicalPayload, and verify HMAC-SHA256. - Open request history and match the saved payload, response, or error with the external server log by
event_id. - Return
2xxonly after the event is accepted or durably queued; do not process a repeatedevent_idagain. - If the secret is lost, create a new webhook and replace the old one using the safe sequence above.