Updates and webhooks

telethon-plus listens for four kinds of incoming events on the account and hands each one to every subscriber: NewMessage, MessageEdited, MessageDeleted and ChatAction (joins, leaves, title changes and the like). There are two ways to receive them, and both can run at once: the websocket, on by default, and a webhook, on when TELETHON_POST_TO_URL is set. TELETHON_UPDATES_ENABLED=false turns event listening off, which removes /ws/updates and stops the webhook.

WebSocket: /ws/updates

const ws = new WebSocket('ws://your-host:8080/ws/updates?token=YOUR_AUTH_KEY');
ws.onmessage = (e) => console.log(JSON.parse(e.data));

Each frame is one event as JSON. When TELETHON_AUTH_KEY is set, pass it as ?token=, because browser websocket clients cannot set an Authorization header. A wrong or missing token refuses the handshake with HTTP 403, so the socket never opens.

Any number of clients can connect, and each gets its own queue of TELETHON_UPDATES_BUFFER_SIZE events (default 256). The server closes a socket in these cases:

Close code When What the client should do
1013 The client fell a whole buffer behind and events were dropped. Reconnect, then read what it missed with GET /api/messages.
1001 The server is shutting down. Reconnect once it is back.

A websocket only delivers events that happen while it is connected. Nothing is replayed after a reconnect, so a client that must not miss anything reads the chats it cares about with GET /api/messages after every connect.

Outbound webhook: TELETHON_POST_TO_URL

Set TELETHON_POST_TO_URL and every event is also POSTed there as JSON, with a TELETHON_POST_TO_TIMEOUT second timeout (default 10). Use it for workers that cannot hold a websocket open.

Delivery is best effort and never blocks Telethon. Events wait in a queue of TELETHON_UPDATES_BUFFER_SIZE, and one that arrives while the queue is full is dropped with a log line. A POST that fails to connect or gets a 5xx answer is logged and not retried; other answers, 4xx included, are not checked. The webhook runs only while TELETHON_UPDATES_ENABLED is on. Every message the account receives goes to that URL, so point it only at an endpoint you control, over HTTPS.

Event payload

{
  "type": "NewMessage",
  "message": {
    "id": 4242,
    "date": "2026-04-29T12:00:00+00:00",
    "text": "hi",
    "out": false,
    "sender_id": 12345,
    "chat_id": -1001234567890,
    "reply_to_msg_id": null,
    "media": false,
    "media_type": null
  },
  "chat_id": -1001234567890
}
Field Meaning
type NewMessage, MessageEdited, MessageDeleted or ChatAction.
message.id Message ID within the chat.
message.date When it was sent, ISO 8601 in UTC.
message.text The text, or the caption of a media message. Empty for media without one.
message.out true when the account itself sent it.
message.media, message.media_type Whether it has an attachment, and Telethon’s type name for it (such as MessageMediaPhoto). Fetch the bytes with download_media.
chat_id The chat in Telethon’s marked form: -100<id> for channels and supergroups, -<id> for basic groups, the plain ID for private chats. Message listings and entity lookups report the plain ID without the prefix.

The payload is small on purpose. Use GET /api/messages or GET /api/entities for the full message or the sender’s profile. MessageDeleted carries deleted_ids instead of a message, and ChatAction carries the fields Telethon gives it, such as user_id.