Table of contents
Webhooks
What are Webhooks
A webhook is a mechanism that allows your application to receive automatic webhooks about events occurring in the MassAccess system. When an event happens — a message status changes, a client phone number is updated, or a client interacts with your bot — MassAccess sends an HTTP POST request to your server with event data in JSON format.
Requests are sent to the Webhook URL configured in your company dashboard (Settings → Webhook URL). Each request includes an HMAC-SHA256 signature (X-Signature header) so you can verify its authenticity.
How to Set Up Webhooks
To start receiving webhooks, log in to your company dashboard, go to Settings, and specify your server URL in the «Webhook URL» field. After saving, MassAccess will begin sending HTTP POST requests to this URL for every event in your company. No additional API calls are needed — webhooks are sent automatically for all message status changes, phone number updates, and client interactions with your bots.
The Check button sends one signed test request with event = webhook.test and data.test = true. It carries the same application delivery headers as business events: X-Signature, X-Webhook-Id, Idempotency-Key, and X-Webhook-Attempt. The address is considered available when the server returns HTTP 2xx; 4xx means the server is reachable but rejected the request, while 5xx, timeout, or a network error means the endpoint is unavailable. The check confirms only that the safe webhook.test event is accepted; it does not guarantee acceptance of every event type. Use real delivery or /api/v1/webhooks/replay for a complete check.
Event Types
MassAccess sends webhooks for the following events:
| Event | Description |
|---|---|
| message.status | Sent when a message reaches a terminal status: sent or failed. The data.state field contains the current status. If data.state is "failed", additional data.error_code and data.error_description fields are included. |
| client.phone.changed | Sent when a client's phone number is changed via the /api/v1/change_phone endpoint. The data.state field is "completed" on success. If the change fails, data.state is "failed" with error_code and error_description fields. |
| client.check | Sent when checking client existence by phone number via the /api/v1/client/check endpoint. data.exists indicates whether the client was found, and data.bots contains the list of bots the client is connected to. |
| client.registered | Sent when a new client registers through your bot in Telegram or MAX. The payload includes phone, bot_name, and service fields. This event is sent once per client registration. |
Webhook Format
Format of incoming webhooks about message status. Notifications are sent to the URL in company settings.
Request Headers
| Header | Description |
|---|---|
| X-Signature | HMAC-SHA256 signature of the request body |
| X-Webhook-Id | Stable event UUID; matches event_id in the body. |
| Idempotency-Key | Deduplication key; matches X-Webhook-Id. |
| X-Webhook-Attempt | Delivery attempt number, starting at 1. |
| Content-Type | application/json |
| User-Agent | MassAccess-Webhook/2.0 |
Payload Structure
Every webhook request body is a JSON object with a common structure. The top-level fields are always present; the contents of the data field depend on the event type. Below are examples for each event type.
Message status change
Sent when a message is successfully delivered to the recipient. data.state = sent.
{
"event": "message.status",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"state": "sent"
}
}Message status change (error)
Sent on delivery failure. data.state = failed + error_code and error_description.
{
"event": "message.status",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"state": "failed",
"error_code": "NETWORK_ERROR",
"error_description": "Network error"
}
}Phone number change
Sent on successful phone number change via API /api/v1/change_phone. data.state = completed.
{
"event": "client.phone.changed",
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"state": "completed"
}
}Phone number change (error)
Sent on phone change failure. data.state = failed + error_code and error_description.
{
"event": "client.phone.changed",
"event_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"state": "failed",
"error_code": "INVALID_PHONE",
"error_description": "Invalid phone number format"
}
}Client Registration in Bot
Sent when a new client registers through a bot (Telegram/MAX). Contains phone, bot_name, service. One-time event.
{
"event": "client.registered",
"event_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"phone": "12345678901",
"bot_name": "my_telegram_bot",
"service": "telegram"
}
}Client Check (exists = true)
Sent when the client with the specified number is found in the company. data.exists = true, data.bots contains a list of bots with name and service fields.
{
"event": "client.check",
"event_id": "3bd290b8-09b4-48c4-920b-85b93fc3859b",
"timestamp": "2026-07-28T21:45:31Z",
"data": {
"exists": true,
"bots": [
{
"name": "main_max",
"service": "max"
}
]
}
}Client Check (exists = false)
Sent when the client with the specified number is not found. data.exists = false, data.bots is empty.
{
"event": "client.check",
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-07-28T21:45:31Z",
"data": {
"exists": false,
"bots": []
}
}Webhook Fields
| Name | Type | Required | Description |
|---|---|---|---|
| event | string | Yes | Event type: message.status | client.phone.changed | client.registered |
| event_id | string (UUID) | Yes | Unique message identifier (UUID) |
| timestamp | string (ISO 8601) | Yes | Event time in ISO 8601 UTC |
| data.state | string | No | Message status: new, processing, sent, failed |
| data.error_code | string | No | Error code (present only on failure) |
| data.error_description | string | No | Human-readable error description (present only on failure) |
| data.client_id | integer | No | Client identifier (for client.* events) |
| data.phone | string | No | Client phone number (for client.* events) |
| data.bot_name | string | No | Bot name that triggered the event (for client.* events) |
| data.exists | boolean | No | Whether the client exists (client.check event) |
| data.bots | array | No | List of bots the client is connected to (client.check event). Each bot has name (string) and service (string) fields. |
Sending Architecture
Your Server Response
To confirm receipt, your server must return HTTP 200 OK. If response is not 200, MassAccess will retry with increasing intervals.
Your server must respond within 10 seconds. If the server returns a non-2xx status or times out, MassAccess retries delivery with exponential backoff. After 5 failed attempts, the webhook is discarded.
| HTTP Status | Description |
|---|---|
| 200 OK | Webhook delivered successfully — MassAccess stops retrying. |
| 4xx / 5xx | Webhook delivery failed — MassAccess will retry with exponential backoff (delays: ~1s, ~5s, ~25s, ~125s, ~625s). |
Signature Verification
The X-Signature header contains an HMAC-SHA256 signature of the raw request body (before JSON parsing). To verify the webhook comes from MassAccess:
- Get the raw HTTP request body as bytes (do not parse JSON first)
- Compute HMAC-SHA256 of the raw body bytes using your API key as the secret
- Compare the resulting HEX string with the X-Signature header. Use a constant-time comparison function from your language's standard library (e.g. compare_digest in Python, hash_equals in PHP, timingSafeEqual in Node.js) — not the regular == operator. Constant-time comparison always takes the same amount of time regardless of whether strings match, preventing attackers from guessing the signature by measuring server response time. If they don't match, reject the request — it is forged.
Verify the X-Signature header (HMAC-SHA256) to ensure webhook authenticity:
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
"""Verify webhook HMAC-SHA256 signature."""
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)Notification Error Codes
Reference of error codes that can be delivered in the error_code field of webhook notifications (message.status, client.phone.changed, etc.). For each code the messenger where it may occur is indicated: Telegram, MAX, both, or «—» for errors not tied to a specific messenger.
Delivery
| Code | Description | Messenger |
|---|---|---|
| TOKEN_INVALID | The bot token is invalid or was revoked by the messenger. Check the token in bot settings. | Telegram · MAX |
| BAD_REQUEST | The messenger rejected the request due to invalid message parameters. | Telegram · MAX |
| RATE_LIMIT | Messenger rate limit exceeded. The message will be retried after a delay. | Telegram · MAX |
| WEBHOOK_CONFLICT | Telegram webhook conflict: another server has already set a webhook for this bot. | Telegram |
| NETWORK_ERROR | Network error while calling the messenger API (connection reset, unreachable). | Telegram · MAX |
| DNS_ERROR | Failed to resolve the messenger API hostname. | Telegram · MAX |
| SSL_ERROR | TLS/SSL error while establishing a secure connection to the messenger API. | Telegram · MAX |
| TIMEOUT | Timed out waiting for a response from the messenger API. | Telegram · MAX |
| CONNECTION_CLOSED | The connection to the messenger API was closed before a response was received. | Telegram · MAX |
| PROXY_ERROR | Proxy relay error (the intermediate delivery server is unavailable). | Telegram · MAX |
| CONFIG_MISSING | The messenger webhook URL is not configured in platform settings. | Telegram · MAX |
| DELIVERY_FAILED | Generic message delivery failure. The cause is unclassified or unknown. | Telegram · MAX |
Recipient
| Code | Description | Messenger |
|---|---|---|
| USER_BLOCKED_BOT | The recipient blocked the bot. Telegram: HTTP 403 «bot was blocked by the user»; MAX: chat.denied. Delivery is terminal (no retries). | Telegram · MAX |
| USER_NOT_FOUND | Recipient not found (not registered in the messenger). | Telegram · MAX |
| USER_INACTIVE | Recipient is inactive or unavailable in the messenger. | Telegram · MAX |
| DIALOG_SUSPENDED | Dialog suspended: MAX returned error.dialog.suspended (recipient blocked the bot or a moderator restriction applies). | MAX |
Phone change
| Code | Description | Messenger |
|---|---|---|
| PHONE_NEVER_EXISTED | The phone number has never been registered in any messenger. | — |
| PHONE_ALREADY_INACTIVE | The phone number is already inactive. | — |
| PHONE_MULTIPLE_CLIENTS | The phone number is linked to several different accounts. | — |
| PHONE_NOT_LINKED | The phone number is not linked to any account. | — |
| PHONE_NOT_IN_BOT | The new phone number has never been registered in any messenger. | — |
| PHONE_NOT_ACTIVE | The new phone number is inactive. | — |
| MIGRATION_SAME_CLIENT | The old and new phone numbers are already linked to the same account. | — |