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:

EventDescription
message.statusSent 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.changedSent 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.checkSent 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.registeredSent 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

HeaderDescription
X-SignatureHMAC-SHA256 signature of the request body
X-Webhook-IdStable event UUID; matches event_id in the body.
Idempotency-KeyDeduplication key; matches X-Webhook-Id.
X-Webhook-AttemptDelivery attempt number, starting at 1.
Content-Typeapplication/json
User-AgentMassAccess-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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "event": "client.check",
  "event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2026-07-28T21:45:31Z",
  "data": {
    "exists": false,
    "bots": []
  }
}

Webhook Fields

NameTypeRequiredDescription
eventstringYesEvent type: message.status | client.phone.changed | client.registered
event_idstring (UUID)YesUnique message identifier (UUID)
timestampstring (ISO 8601)YesEvent time in ISO 8601 UTC
data.statestringNoMessage status: new, processing, sent, failed
data.error_codestringNoError code (present only on failure)
data.error_descriptionstringNoHuman-readable error description (present only on failure)
data.client_idintegerNoClient identifier (for client.* events)
data.phonestringNoClient phone number (for client.* events)
data.bot_namestringNoBot name that triggered the event (for client.* events)
data.existsbooleanNoWhether the client exists (client.check event)
data.botsarrayNoList 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 StatusDescription
200 OKWebhook delivered successfully — MassAccess stops retrying.
4xx / 5xxWebhook 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:

  1. Get the raw HTTP request body as bytes (do not parse JSON first)
  2. Compute HMAC-SHA256 of the raw body bytes using your API key as the secret
  3. 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:

python
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

CodeDescriptionMessenger
TOKEN_INVALIDThe bot token is invalid or was revoked by the messenger. Check the token in bot settings.Telegram · MAX
BAD_REQUESTThe messenger rejected the request due to invalid message parameters.Telegram · MAX
RATE_LIMITMessenger rate limit exceeded. The message will be retried after a delay.Telegram · MAX
WEBHOOK_CONFLICTTelegram webhook conflict: another server has already set a webhook for this bot.Telegram
NETWORK_ERRORNetwork error while calling the messenger API (connection reset, unreachable).Telegram · MAX
DNS_ERRORFailed to resolve the messenger API hostname.Telegram · MAX
SSL_ERRORTLS/SSL error while establishing a secure connection to the messenger API.Telegram · MAX
TIMEOUTTimed out waiting for a response from the messenger API.Telegram · MAX
CONNECTION_CLOSEDThe connection to the messenger API was closed before a response was received.Telegram · MAX
PROXY_ERRORProxy relay error (the intermediate delivery server is unavailable).Telegram · MAX
CONFIG_MISSINGThe messenger webhook URL is not configured in platform settings.Telegram · MAX
DELIVERY_FAILEDGeneric message delivery failure. The cause is unclassified or unknown.Telegram · MAX

Recipient

CodeDescriptionMessenger
USER_BLOCKED_BOTThe 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_FOUNDRecipient not found (not registered in the messenger).Telegram · MAX
USER_INACTIVERecipient is inactive or unavailable in the messenger.Telegram · MAX
DIALOG_SUSPENDEDDialog suspended: MAX returned error.dialog.suspended (recipient blocked the bot or a moderator restriction applies).MAX

Phone change

CodeDescriptionMessenger
PHONE_NEVER_EXISTEDThe phone number has never been registered in any messenger.
PHONE_ALREADY_INACTIVEThe phone number is already inactive.
PHONE_MULTIPLE_CLIENTSThe phone number is linked to several different accounts.
PHONE_NOT_LINKEDThe phone number is not linked to any account.
PHONE_NOT_IN_BOTThe new phone number has never been registered in any messenger.
PHONE_NOT_ACTIVEThe new phone number is inactive.
MIGRATION_SAME_CLIENTThe old and new phone numbers are already linked to the same account.