Bot API reference
The error that means stop sending
Sending to a user who has blocked the bot fails with HTTP 403 and the description "Forbidden: bot was blocked by the user". It is permanent until the user unblocks, a bot cannot reverse it, and it is the only unambiguous per-recipient delivery signal the Bot API provides.
Blocked by the user: the exact figures
- Error
- HTTP 403 — "Forbidden: bot was blocked by the user"
- Error code
- HTTP 403
- Description
- Forbidden: bot was blocked by the user
- Reversible by the bot
- No. Only the user can unblock
- Detectable in advance
- No. Discovered by attempting a send
- Correct handling
- Retire the subscriber once, on first occurrence
As of 2025-10-01, Telegram Bot API 13.4
What it means in practice
A bot cannot start a conversation. It may only message someone who has messaged it first, and a block revokes that permission — so this error is the mirror image of the rule that governs every outbound message. There is no API call to request re-permission, no notification when a user blocks, and no way to detect the state before attempting a send. You discover it by trying.
That makes it operationally valuable rather than merely annoying. Where email produces soft bounces, spam folders and silent non-delivery, Telegram produces one explicit, catchable, permanent signal. A subscriber list that removes users on this error stays accurate without any inference, which is a better position than most channels offer.
The handling has to be idempotent and immediate. Marking the subscriber inactive on the first occurrence, inside the same transaction that records the send attempt, stops the list re-attempting on the next campaign. Bots that log the error and continue re-attempting every send spend a growing share of their rate budget on recipients who will never receive anything — and on a large list that is a real cost, since the ceiling is bot-wide.
It is worth distinguishing from its neighbours, because the correct response differs. "Chat not found" usually means the user never started the bot, or the chat id is wrong. "User is deactivated" means the Telegram account is gone, which is also permanent but for a different reason. "Bot was kicked from the group" is the group equivalent. All are 403s and all mean stop, but only the blocked case can be reversed by the user later, so the record should distinguish them rather than flattening everything into a single inactive flag.
One product consequence deserves stating during scoping. Because a block is permanent from the bot's side and invisible until a send is attempted, campaign metrics degrade quietly: a list that looks like forty thousand subscribers may contain several thousand who blocked months ago. Reconciling that requires attempting the send, which means the accurate number is only ever known after a campaign rather than before it.
Handling it in code
// Distinguish the permanent 403s rather than flattening them into one flag.
type Terminal = 'blocked' | 'deactivated' | 'chat-missing'
function terminalReason(error: unknown): Terminal | undefined {
const e = error as { error_code?: number; description?: string }
if (e.error_code !== 403 && e.error_code !== 400) return undefined
const d = e.description ?? ''
if (d.includes('blocked by the user')) return 'blocked'
if (d.includes('user is deactivated')) return 'deactivated'
if (d.includes('chat not found')) return 'chat-missing'
return undefined
}
async function sendOrRetire(chatId: number, text: string): Promise<void> {
try {
await bot.api.sendMessage(chatId, text)
} catch (error) {
const reason = terminalReason(error)
if (reason === undefined) throw error
// Permanent. Retire once, and never spend rate budget on this recipient again.
await retireSubscriber(chatId, reason)
}
}Questions this raises
Is the bot notified when someone blocks it?
For a private chat, no — there is no update announcing it, and the state is only discovered on the next send. In groups the situation differs: a bot removed from a group does receive a `my_chat_member` update, which is why group membership can be tracked accurately and private-chat blocks cannot.
Can I ask the user to unblock, or message them another way?
No. A block removes the permission that lets the bot message them at all, and there is no fallback channel. If they return through a deep link and start the bot again, the permission returns with them — but that is entirely their action.
Should a blocked user be deleted or just flagged?
Flagged, with the reason and a timestamp. Deleting loses the history and means a user who unblocks and returns looks like a new subscriber, which distorts every retention number you might later want. It also loses the information that they left, which is usually the more interesting fact.
Does a block affect a user's membership of channels the bot manages?
No. Blocking the bot stops the bot messaging them privately; it does not remove them from a channel or group. A gated-access bot therefore has to treat channel membership and messageability as separate states, because a member who has blocked the bot still has access but can no longer be told anything.
Related limits
Why this error is the only definitive per-recipient signal is explained in the absence of delivery receipts.
A product where an inaccurate subscriber count directly distorts the reporting is the loyalty build.