Updates and long polling
Everything that happens to your bot arrives as an update. You receive updates either by long polling getUpdates or through a webhook — never both. At launch only getUpdates is available; webhooks open later.
Update types#
An Update is {"update_id": …, "<type>": {…}} with exactly one type:
| Type | When | Default |
|---|---|---|
message | A person writes in a private chat with the bot, or in a group where the privacy rules let the bot see it. | yes |
edited_message | Such a message was edited and the bot would receive it now (the privacy rules are applied to the new text). | yes |
channel_post, edited_channel_post | A post in a channel the bot was added to, or its edit. | yes |
callback_query | Someone pressed one of the bot's buttons. | yes |
my_chat_member | The bot was added, its rights changed, it was removed or left, or its grant was suspended because the group became encrypted; in encrypted groups, attached or detached. | yes |
bot_started | A person pressed Start (with start_param if the link had one). | yes |
bot_blocked | A person stopped or blocked the bot. | yes |
encrypted_message | Encrypted bots: an end-to-end encrypted message or button press (see Encrypted bots). | yes |
chat_member | A member of a group joined, left, was banned or unbanned, muted or unmuted. | no — opt in |
chat_member is delivered only to bots that list it in allowed_updates and either read all messages (privacy off with can_read_all) or hold can_restrict_members or can_ban_members in that group. Groups only.
Set the types you want with allowed_updates on getUpdates or setWebhook. The list is stored on the bot and applies until you change it; [] restores the default set. Handle unknown types gracefully: new ones may be added.
Examples#
A command in a group
{"update_id": 1043,
"message": {"message_id": 4503599627502817, "date": 1759590000,
"chat": {"id": -4503600130020245, "type": "group", "title": "Runners TLV", "username": "runnerstlv", "is_e2ee": false},
"from": {"id": 4503600212045337, "is_bot": false, "first_name": "Dana", "username": "dana"},
"text": "/poll@pollbot Lunch? Pizza / Sushi",
"entities": [{"type": "bot_command", "offset": 0, "length": 13}],
"command": {"name": "poll", "args": "Lunch? Pizza / Sushi", "addressed_to_me": true}}}
The bot was added to a group
{"update_id": 1001,
"my_chat_member": {"chat": {"id": -4503600130020245, "type": "group", "title": "Runners TLV", "is_e2ee": false},
"from": {"id": 4503600212045337, "is_bot": false, "first_name": "Dana"},
"date": 1759589000, "old_status": "left", "new_status": "member",
"rights": {"can_send": true, "can_send_media": true}, "mode": "server",
"consent_receipt_id": "01jb2f6x9q0v7m4c8d1e5k3h2n"}}
A person pressed Start
{"update_id": 1002,
"bot_started": {"from": {"id": 4503600212045337, "is_bot": false, "first_name": "Dana"},
"chat": {"id": 4503600212045337, "type": "private", "first_name": "Dana", "is_e2ee": false},
"start_param": "spring-campaign", "date": 1759589100}}
A button press
{"update_id": 1050,
"callback_query": {"id": "01JB2G0M3K6V1S8T4W2Q9R5X7Y",
"from": {"id": 4503600212045337, "is_bot": false, "first_name": "Dana"},
"message": {"message_id": 4503599627502900, "date": 1759590100,
"chat": {"id": -4503600130020245, "type": "group", "title": "Runners TLV", "is_e2ee": false},
"from": {"id": 7123456789, "is_bot": true, "first_name": "Poll", "username": "pollbot"},
"text": "Lunch?",
"reply_markup": {"inline_keyboard": [[{"text": "Pizza", "callback_data": "v:pizza"}, {"text": "Sushi", "callback_data": "v:sushi"}]]}},
"data": "v:pizza"}}
Rights in my_chat_member list only the rights that are on. Names never include a phone number; see what a bot learns about people.
Long polling with getUpdates#
POST https://bots.qivuna.com/v1/getUpdates
{"offset": 1044, "limit": 100, "timeout": 25}
- If a webhook is set:
409 conflict(“A webhook is active; call deleteWebhook first”). - If another
getUpdatesof this bot is running:409 conflict. Run exactly one poller per bot. offsetacknowledges every update with a lowerupdate_id: they are deleted.- Up to
limitwaiting updates are returned at once, oldest first. - If none are waiting and
timeout> 0, the call waits up totimeoutseconds (25 at most) for the first one. If the platform has no free long-poll slot, or your bots (all bots of one owner together) already have 4 polls waiting, it answers[]at once withRetry-After: 2: wait two seconds and poll again.
Updates you received are not deleted until a later call acknowledges them. If your process dies before acknowledging, the next call returns them again — that is how nothing gets lost, and also why the same update can reach you twice.
Keep your HTTP client's read timeout above the long-poll timeout (for timeout: 25, at least 30 seconds).
Ordering#
update_idincreases for your bot.getUpdatesreturns updates in that order.- Webhooks deliver one update at a time per chat — a group, a channel, or the 1:1 chat with one person — in order, and different chats in parallel up to
max_connections. A later update of a chat never overtakes an earlier one that is waiting for a retry. Button presses are ordered with the chat they were pressed in. update_idvalues are not consecutive and may jump (for example by 1,000 if our cache was reset). Never assume gaps mean loss.
Expiry and limits#
- An update is kept for 24 hours. After that it is gone, acknowledged or not.
- At most 10,000 updates wait per bot. Beyond that the oldest are dropped. A bot that never reads cannot build an endless backlog.
- While the platform is paused (
503 platform_paused), nothing new is queued: events in that time are not delivered later.
Handle updates at least once#
Delivery is at least once. The same update can arrive again after a crash, a lost response or a webhook retry, with the same update_id. In rare cases (a message with files whose delivery was interrupted part-way) the same message can arrive as two updates with different update_ids; compare message_id if a repeat would matter.
- Remember the
update_ids you handled (a day is enough) and skip repeats. - Make side effects idempotent: key database writes and outgoing calls on the update. For outgoing API calls, use an
Idempotency-Keyderived from the update. - Acknowledge only after the handler finished. The SDKs do all of this, including leases so a slow handler is not run twice.
What is never delivered#
- Messages sent by bots, including your own.
- Room lines written by the platform (“Dana added @bot”) and the app's system messages.
- Disappearing messages, secret chats, end-to-end encrypted group messages (bots in encrypted groups receive only what is addressed to them).
- Edits of end-to-end encrypted messages.