Telegram Join Request Bot: The 5-Minute Window Most Admins Miss
Telegram Join Request Bot: The 5-Minute Window Most Admins Miss
Telegram has a verification mechanism that does what a captcha cannot, and most admins do not know what it gives them.
Create an invite link with creates_join_request=True and users request to join rather than joining. Your bot receives a ChatJoinRequest, and you approve or decline with approveChatJoinRequest / declineChatJoinRequest — both requiring the can_invite_users admin right.
The interesting part is what the request object contains, and one line in the documentation that almost nobody has read.
What a join request gives you
The user's bio. ChatJoinRequest carries an Optional bio field — present only when the user has set a profile bio. When it is present you can read it before deciding to admit them. Farm accounts that monetise their bio advertise in it, and reading bio costs you nothing: it already arrived with the update, no extra API call and no friction for the user. It will not catch an account that left its bio empty.
Their full user object, including language_code, username, and whether they have is_premium. A Telegram Premium account is not proof of legitimacy, but it costs money to maintain, which changes the economics for a disposable farm account.
Timestamp. Join bursts are visible: several requests within a short window is a raid signature.
And this, from the documentation for user_chat_id:
"The bot can use this identifier for 5 minutes to send messages until the join request is processed, assuming no other administrator contacted the user."
You can message someone who has requested to join, before they are a member, for five minutes.
That is a verification channel that does not exist anywhere else in the platform, and it is the single most underused capability in Telegram group management.
What the five-minute window is for
You can hold a private exchange with a prospective member outside your group. The challenge happens in a one-to-one chat, not in front of your community.
That solves several problems at once:
No captcha clutter in the group. Verification messages never touch your channel. No bot noise, no deleted challenge messages, no confusion for existing members.
You can ask a real question. "What brings you here?" or a topic-specific question. Free-text answers from a bot farm are obviously wrong in a way a tapped button never is.
You can state rules and get agreement before admission rather than hoping people read a pinned post.
A non-response is itself a signal. An account that requests to join and ignores a direct message for five minutes is not eager to participate.
The constraint is real, though: five minutes, and only "assuming no other administrator contacted the user." Design for a fast exchange — one message, one expected reply — not a conversation. And do not build a flow that breaks if the window closes; fall back to holding the request for human review.
Why this beats captcha
| Captcha | Join request | |
|---|---|---|
| Friction for legitimate users | Visible, always | Very low if auto-approved |
| Signal available before entry | None | Bio, username, premium, timing |
| Happens in the group | Usually | No — private |
| Stops delayed-drip farms | No | Only if the account advertises in its bio — we have no measurement of how often that holds |
| Leaked link still grants access | Yes | No — approval is required |
That last row matters for paid channels specifically. A captcha protects a link that still admits anyone who solves it. A join request means a leaked link grants nothing — every entry passes through your approval, where you check entitlement. See invite link leaks. For what the captcha side of this table costs in practice, see captcha types compared.
The one thing you must get right
Approve fast.
The entire advantage collapses if requests sit in a queue. A user who taps "Request to join" and hears nothing for four hours has been given worse friction than any captcha would have imposed — an unbounded, unexplained wait.
Auto-approve the clear cases. Hold only the flagged ones for a human. Target seconds, not minutes.
A workable policy:
Auto-approve when the bio is present and clean, the account has a username or getUserProfilePhotos returns at least one photo, and there is no join burst from that link. An absent bio is no signal, not a clean one.
Challenge in the five-minute window when signals are mixed — one soft flag, or a brand-new-looking account.
Hold for review on two or more spam signals.
Auto-decline only on unambiguous combinations — links in bio plus known scam phrasing plus a burst. Score, don't execute on one signal; plenty of legitimate creators have links in their bio.
The trade-off, honestly
creates_join_request and member_limit are mutually exclusive. The documentation is explicit: "If True, member_limit can't be specified."
So you give up the automatic per-link cap and gain an approval decision. For most groups that is a good trade — the cap was never a strong control anyway, since member_limit counts simultaneous members and frees up when someone leaves.
The other cost is operational: you now have a queue, and a queue that nobody watches is worse than no queue. Do not switch to join requests unless approval is automated or somebody is genuinely watching.
Setting it up
- Create the link with
creates_join_request=True - Handle the
chat_join_requestupdate - Score the request — bio, username, premium, burst detection, plus a profile photo lookup via
getUserProfilePhotosif you want that signal - Auto-approve, challenge via
user_chat_id, or hold - Approve within seconds where you can
- Log everything, including
invite_link, for measuring drop-off
Then still apply new-member restrictions after admission. Join requests filter at the door; restrictions cover the account that gets through and waits.
Field definitions and constraints quoted from Telegram's Bot API documentation, verified September 2026.