GLOSSARY

What is an Invite Link Hash?

An invite link hash is the opaque token at the end of a private Telegram invite link, the part after t.me/+ or t.me/joinchat/. It is the only handle a private group or channel exposes to someone who is not a member, and Telegram's API takes it as a plain string in messages.checkChatInvite and messages.importChatInvite.

Free forever plan · No credit card required · Cancel anytime

Quick definition

An invite link hash is the opaque token at the end of a private Telegram invite link, the part after t.me/+ or t.me/joinchat/. It is the only handle a private group or channel exposes to someone who is not a member, and Telegram's API takes it as a plain string in messages.checkChatInvite and messages.importChatInvite.

In a single sentence: a username says "this chat is public", an invite hash says "you were given a key".

What it means

Telegram chats come in two discoverability modes. A public supergroup or channel has a username, so anyone can find it and anyone can resolve t.me/somechannel into a peer. A private one has no username, and the only way to reference it from outside is an invite link.

The link looks like https://t.me/+AbCdEfGhIjKlMnOp, or in the older form https://t.me/joinchat/AbCdEfGhIjKlMnOp. The trailing token is the invite link hash, an opaque base64url-style string. It is not derived from the chat ID and you cannot compute one; it is issued by Telegram when an admin creates the link, and it is the entire credential.

Two API methods consume it. messages.checkChatInvite(hash) inspects the link without joining, and messages.importChatInvite(hash) actually joins. The distinction matters more than it sounds, because joining is expensive in rate-limit terms and checking is cheap.

Check before you join

checkChatInvite returns one of three shapes, and each tells you something different:

  • chatInvite: you are not a member. You get the title, the participant count, the photo and a small sample of participants. This is the preview a Telegram client shows you before the Join button.
  • chatInviteAlready: you are already in. The response carries the chat itself, which conveniently gives you a usable peer with an access hash for free.
  • chatInvitePeek: temporary read access with an expiry, which some links grant so a person can look inside before committing.

The operational value is filtering. Given a list of 400 collected invite links, checking them costs far less than joining them, and it lets you discard the dead ones, skip the chats you are already in, and rank the rest by member count before you spend a single join. Teams that skip this step burn their join budget on expired links and then wonder why the account is sitting on a multi-hour flood wait.

What an invite link actually carries

On the admin side, a link is not just a string. The chatInviteExported constructor carries the link itself plus the metadata that governs it: admin_id for who created it, date, an optional title so humans can tell links apart, expire_date, usage_limit, the running usage count, and flags for revoked, permanent and request_needed.

Every chat has one primary link, and revoking it immediately generates a replacement. Beyond that, admins can create as many additional links as they want, each with its own expiry, cap and name. Each admin only sees and manages their own set.

The Bot API exposes the same model with different names: createChatInviteLink, editChatInviteLink, revokeChatInviteLink, and a ChatInviteLink object with member_limit, creates_join_request, name, and pending_join_request_count. A bot can only edit or revoke links it created itself.

The attribution trick

Here is the feature almost nobody uses, and it turns a Telegram community from an unmeasurable blob into a real acquisition channel.

Because a chat can have unlimited named invite links, you can mint one per source: one for the website footer, one for the newsletter, one for each paid campaign, one per partner. When someone joins, Telegram tells you which link they used. Over the Bot API that arrives on the chat_member update as an invite_link object containing the name you gave it. For approval-gated links, the same information reaches you on the chat_join_request update before you decide whether to approve.

That is per-source join attribution with no tracking pixel, no redirect, and no cookie. Wire the update into your CRM and a new community member arrives already tagged with the campaign that produced them.

Joining is the expensive part

Importing invite hashes at volume is one of the fastest ways to get an account restricted, and it is worth being explicit about why.

Joining is a strong behavioural signal. Real humans join a few chats a week. An account joining dozens of private groups in an hour matches the profile of scraping and mass-promotion tooling, which is exactly what Telegram's abuse systems are tuned to catch. The response escalates from short flood waits on importChatInvite to waits measured in hours.

There is also a hard ceiling that has nothing to do with pacing. An account can belong to 500 channels and supergroups, or 1000 with Telegram Premium. Cross it and every further join fails with CHANNELS_TOO_MUCH regardless of how slowly you go. Any system that joins groups continuously needs a leave policy, not just a join policy.

Why it matters

For community operators, invite links are the membership funnel: expiry and usage caps are the access control, approval gates are the spam filter, and named links are the analytics. Handled well, a private Telegram community becomes something you can grow deliberately.

For anyone doing research or lead generation on Telegram, invite hashes are the entry point to every group that is not publicly listed, which is most of the interesting ones. Handling them carefully, checking before joining, pacing imports, and rotating across accounts, is the difference between a research pipeline that runs for months and one that burns three phone numbers in a week.

Common mistakes

  • Parsing the hash with a naive split. Links arrive with tracking parameters, trailing punctuation, and both the + and joinchat/ forms. Normalise properly or you will generate a stream of INVITE_HASH_INVALID.
  • Treating INVITE_REQUEST_SENT as an error. It is a success with a pending state. Storing it as a failure means you never notice when the admin approves.
  • Joining to find out whether the link works. checkChatInvite answers that question for a fraction of the cost.
  • Ignoring the 500-channel ceiling. Without a leave policy every long-running account eventually stops being able to join anything.
  • Publishing a link you meant to be private. There is no per-person binding. Use single-use links with short expiries for anything sensitive.
  • Using one link for every campaign. You are throwing away free attribution that Telegram hands you on the membership update.

Related concepts

  • Supergroup: the chat type most invite links point at.
  • Access hash: what you get back once the join succeeds and the chat becomes referenceable.
  • Flood wait: the thing that punishes aggressive joining.
  • Peer ID: how the joined chat is identified afterwards.
  • Telegram Bot API: the admin-side methods for creating and revoking links.
  • Webhook: how join events reach your CRM in real time.

How Pinlyx handles it

Pinlyx normalises every inbound link form, both t.me/+ and the legacy joinchat path, and validates with checkChatInvite before any account spends a join on it. Joins are paced per account, tracked against the channel-membership ceiling, and paused automatically when a FLOOD_WAIT comes back from importChatInvite. Pending join requests are stored as their own state rather than as failures, so an approval that lands three days later still resolves to the right record. Named invite links flow through to contact attribution, so a member who joined from a campaign link arrives in the Telegram CRM already tagged with their source.

Cheat sheet · parse, check, then join

The safe order of operations.

Parsing is where the bugs live, and checking is what keeps the account alive.

// Both of these carry the SAME kind of token
https://t.me/+AbCdEfGhIjKlMnOp
https://t.me/joinchat/AbCdEfGhIjKlMnOp

// This one does NOT. It is a public username.
https://t.me/somechannel        -> contacts.resolveUsername

function parseInvite(url) {
  const u = new URL(url);                       // throws on junk input
  if (u.hostname !== 't.me') return null;
  const path = u.pathname.replace(/^\//, '');    // strip leading slash
  if (path.startsWith('+'))         return { kind: 'invite',   hash: path.slice(1) };
  if (path.startsWith('joinchat/')) return { kind: 'invite',   hash: path.slice(9) };
  return { kind: 'username', username: path };  // different method entirely
}

// Cheap: inspect without joining, without spending join budget
messages.checkChatInvite(hash) -> {
  "_": "chatInvite",
  "title": "Istanbul SaaS Founders",
  "participants_count": 4128,
  "request_needed": true          // importing will NOT join you
}

// Expensive: the call that actually costs rate-limit budget
messages.importChatInvite(hash)

// The response people misread as a failure
{
  "_": "rpc_error",
  "error_code": 400,
  "error_message": "INVITE_REQUEST_SENT"
}
// This is success-with-pending. An admin has to approve.

Two link families

Usernames and invite hashes are resolved by different methods. Never guess.

Check is cheap

Validate the whole batch first, then join only what is worth joining.

500 channels, hard stop

1000 with Premium. Past that, every join fails no matter how slowly you go.

Decision table · invite errors

Six responses to importChatInvite, and what each one means.

INVITE_HASH_INVALID

The hash is malformed or was never valid. Usually a truncated paste, a URL that kept its query string, or a public username link fed to the wrong method.

Do this: Fix the parsing. Retrying will never help.

INVITE_HASH_EXPIRED

The link was revoked by an admin, passed its expire_date, or hit its usage_limit.

Do this: Ask for a new link. The chat is fine, the token is not.

INVITE_REQUEST_SENT

Not a failure. The link requires admin approval, so a join request was created and is now pending.

Do this: Record the pending state and wait for the approval update.

USER_ALREADY_PARTICIPANT

This account is already in the chat.

Do this: Treat as success and reconcile your membership table.

USERS_TOO_MUCH

The target chat has reached its member ceiling.

Do this: Nothing to retry. The chat is full.

CHANNELS_TOO_MUCH

Your own account is in too many channels and supergroups. The ceiling is 500 for a standard account and 1000 with Telegram Premium.

Do this: Leave dormant channels before joining more, or use another account.

Invite link checklist

Six rules for working with invite links at scale.

  • Normalise both the t.me/+ and the joinchat/ forms before touching the API.
  • Always checkChatInvite before importChatInvite. Checking is cheap, joining is not.
  • Pace joins per account and stop the account entirely on a long flood wait.
  • Track total channel membership and leave dormant chats before you hit 500.
  • Store INVITE_REQUEST_SENT as a pending state, not as a failed join.
  • Mint one named invite link per campaign so joins arrive already attributed.

Invite link hashes: FAQ

What community operators and researchers both need to know before importing a link.

They are resolved by completely different API methods. A t.me/mychannel link is a public username, resolved with contacts.resolveUsername, and it works for anyone because the channel chose to be publicly discoverable. A t.me/+AbCdEf link carries an invite hash for a private chat that has no username at all, and it is resolved with messages.checkChatInvite. Feeding one to the other method returns an error, and mixing the two is the most common parsing bug in Telegram integrations.
Partially. messages.checkChatInvite returns a chatInvite object with the title, the participant count, the photo and a small sample of members, without joining. Some links additionally allow a chatInvitePeek response, which grants temporary read access that expires. Neither gives you the member list, and neither counts as joining, which makes checkChatInvite the correct way to validate a batch of links before spending any join budget on them.
Telegram does not publish the figure and tunes it against account age and behaviour, but a few hundred joins in a day is where accounts reliably start collecting long flood waits, and there is a separate hard ceiling on total membership: 500 channels and supergroups for a standard account, 1000 with Premium. In practice, treat joining as one of the most expensive actions on the platform. A warm account joining a handful of groups a day is invisible; the same account joining fifty is not.
A link created with the request_needed flag set, which Telegram surfaces in the interface as "approve new members". Importing it does not add you to the chat, it creates a pending request and the API returns INVITE_REQUEST_SENT. An admin then approves or declines. Over the Bot API the corresponding pieces are createChatInviteLink with creates_join_request, the chat_join_request update, and approveChatJoinRequest or declineChatJoinRequest.
Yes, and it is one of the genuinely good uses of the feature. An admin can create many named invite links for the same chat, one per source, then read back which link a new member used. Over the Bot API, the chat_member update carries an invite_link field describing the exact link that was used, including the name you gave it. That turns a Telegram community into something you can actually measure per channel, rather than a black box that gains members from somewhere.
Treat it as one. Anyone holding it can join, or at minimum can see the chat title and member count. There is no per-person binding: a link posted publicly is a link anyone can use. The controls Telegram gives you are expiry, usage limits, approval requirements and revocation, and the safe pattern for anything sensitive is a single-use link with a short expiry, generated per invitee.
Ready to ship

Every join, paced. Every member, attributed.

Pinlyx validates invite links before it spends a join, respects per-account ceilings, and tags new members with the campaign link they came from.

Free forever plan · GDPR-ready · No credit card required

We value your privacy

We use cookies to improve our site, analyze traffic, and personalize ads. You can accept all, reject non-essential, or customize your choices. Read our Cookie Policy.