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.
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 |
| 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)