Telegram sign-in and rewards

Let members sign in with Telegram, connect your groups and channels, check who joined, and reward messages and reactions.

If your community lives on Telegram, members can sign in with their Telegram account and earn rewards for what they do in your groups. For example:

  • "Join our Telegram group", checked automatically.
  • "Get a ❤ from an admin" on a message you posted in the group.
  • "Join the conversation", a daily reward for posting in the group.
  • "@ana just received +10 XP by getting a ⚡ from @sam", posted in the group whenever an admin tips.
  • Sign in without a password inside your own Telegram Mini App.

Everything runs through Domino's own Telegram bot. You never create a bot for your groups or handle a token.

What members see

  • A "Continue with Telegram" button in your app on the web, called Telegram Login.
  • Instant sign-in inside your Mini App. A member who opens your Mini App in Telegram is signed in, or signed up, without a button.
  • One account, either way. Both reach the same Telegram account, so a member who signs in once each way is still one member.
  • A "Connect Telegram" button for members who signed in another way.
  • Rewards for group activity, such as joining a group, posting in it, or getting a reaction from an admin.
  • A message in the group when someone earns, if the quest announces it.

Domino stores the member's numeric Telegram ID, name, @username and profile photo. It never asks for their phone number, and it never needs your bot's token.

Telegram LoginMini App sign-in
WhereYour app in a browserYour Mini App, opened inside Telegram
BotDomino's own botYour bot, the one your Mini App runs on
Turned on byDomino, for the platformYou, per project and environment, in Console
What it provesTelegram's OpenID Connect sign-inTelegram's signature on the Mini App's launch data

What you can do in Console

Without codeWhere
Turn on sign-in inside your Mini AppSettings → Sign-in, Telegram Mini App
Connect groups and channels, see their status, remove themIntegrations, Telegram
Pick the group a quest announces inThe quest's Settings
See a member's announcements, retry one that failedThe member's page, Announcements
Unlink or relink a member's Telegram accountThe member's page, Identities

The sign-in buttons, the join quest and reaction rewards come from code. The starters already show the Telegram Login button.

Offer Telegram Login

Telegram Login is available once Domino has turned it on for the platform. The starters' sign-in components already show the button, with the other providers'; in your own app, render one button per sign-in provider:

import { useSignInOptions } from "@domino-sdk/relay/react"

export function ProviderButtons() {
  const signIn = useSignInOptions()

  return (signIn.signInProviders ?? []).map((provider) => (
    <button
      key={provider.id}
      type="button"
      disabled={signIn.isPending}
      onClick={() => void provider.start()}
    >
      Continue with {provider.label}
    </button>
  ))
}

The button takes the member to Telegram's consent screen, and Telegram sends them back signed in, or signed up.

Sign in inside your Mini App

Name your bot in Console

Your Mini App must be your app: in @BotFather, point the bot's Mini App URL at the address Domino serves your app on. Domino refuses sign-in from any other address.

Open Settings → Sign-in, enter your bot's ID under Telegram Mini App, and save. The ID is the number before the colon in the bot's token, such as 123456789 in 123456789:AA…. Test and Live each have their own setting. Clear the field to turn Mini App sign-in off.

Sign in when the Mini App opens

Telegram opens your Mini App with signed launch data. Your app reads it and signs the member in, or up:

import { createBrowserRelayClient } from "@domino-sdk/relay/browser"
import {
  telegramMiniAppInitData,
  withTelegram,
} from "@domino-sdk/relay/telegram"

const relay = withTelegram(createBrowserRelayClient({ baseUrl: "/relay" }))
const initData = telegramMiniAppInitData()

if (initData && !(await relay.auth.session())) {
  await relay.telegram.miniApp.signIn(initData)
}

Domino checks Telegram's signature for the bot you named, so launch data from another bot, or edited data, is refused. It accepts launch data at most five minutes old, and each launch signs in once. Check for a session first, as above, so a reload doesn't try again.

Connect Telegram to a signed-in member

A member who signed in another way connects Telegram from your app:

import { useAccount } from "@domino-sdk/relay/react"

export function TelegramAccount() {
  const telegram = useAccount("telegram")

  if (telegram.isConnected) {
    return (
      <button type="button" onClick={() => void telegram.disconnect()}>
        Disconnect Telegram
      </button>
    )
  }

  return (
    <button type="button" onClick={() => void telegram.connect()}>
      Connect Telegram
    </button>
  )
}

A Telegram account another member already uses can't be connected.

Connect a group or channel

Domino's bot reads your chats as an administrator.

Start in Console

Open Integrations, open the Telegram card, and choose Add chat. Console shows a one-time link that works for ten minutes.

Choose the chat in Telegram

Open the link and start the bot. It first says which organization and project the chat will be connected to: your organization's name, the project's name, its slug and the environment. Check it, then choose Choose a group or Choose a channel. Telegram lists only chats you administer, and adds Domino's bot to the one you pick as an administrator. The bot confirms the chat it connected and the project again.

Whoever opens the link connects the chat, so if you send it to another chat administrator, tell them which project to expect.

Return to Console

The chat appears in the list once Domino has checked that you and the bot are both administrators. Connect as many chats as the project needs; Test and Live each have their own.

Every Domino organization shares the same bot. If the bot already has an administrator right in the chat that you don't, such as posting messages in a channel, the bot replies with the rights you're missing and connects nothing. The chat's owner, or an administrator with those rights, connects it instead.

StatusMeaning
ReadyThe bot is an administrator and reads the chat
Needs attentionThe bot was removed or is no longer an administrator; the issue says what to do

Remove chat disconnects a chat from the project and environment. The bot leaves the chat once no project uses it.

Check membership in a quest

A join quest checks that the member is in one of your connected groups or channels. You pick the chat in Console from the ones you connected. The telegram-membership snippet is a complete join quest; install it as described in Your first quest.

The member needs their Telegram account in your app, by signing in with it or connecting it.

import {
  decision,
  defineQuest,
  manual,
  once,
  settings,
} from "@domino-sdk/relay/authoring"

export default defineQuest({
  key: "join-telegram",
  title: "Join our Telegram group",
  settings: settings.object({
    chat: settings.telegramChat({ label: "Telegram group" }),
  }),
  trigger: manual(),
  completion: once(),
  rewards: [],
  async evaluate(ctx) {
    // No chat chosen yet: nothing to check. Relay supplies ctx.telegram to every quest.
    const member = ctx.settings.chat
      ? await ctx.telegram!.member({ chat: ctx.settings.chat })
      : null

    return member?.isMember
      ? decision.accept()
      : decision.reject({
          reason: "Link your Telegram account and join the group, then try again.",
        })
  },
})

Require a linked Telegram account

A quest can also ask only that the member linked Telegram, such as an onboarding quest that wants a wallet, X and Telegram connected. It needs no connected chat. An account your app vouched for at sign-in, or one a member import brought in, counts like one the member connected.

Reward reactions

Reward members for reactions in your connected groups, such as paying the author of a message when an admin reacts to it with a heart.

  • Linked accounts only. Domino records a reaction only when the person reacting, or the author of the message, has linked Telegram in the project. Reacting to your own message doesn't count.
  • What the bot saw. Domino knows who wrote a message only if it was posted while the bot was in the chat. Telegram sends no reactions from before the bot became an administrator.
  • Groups, not channels. Telegram doesn't say who reacted to a channel post, so channel reactions are never recorded.
  • Removing a reaction marks it removed. Adding the same reaction again later doesn't pay twice.

Your developer sets this up as a tracker, Domino's running record of activity, and a quest that pays from it:

import {
  automatic,
  decision,
  defineQuest,
  defineTracker,
  settings,
  telegram,
} from "@domino-sdk/relay/authoring"

export const reactions = defineTracker({
  key: "telegram-reactions",
  title: "Telegram reactions",
  source: telegram.reactions(),
  window: { start: Date.parse("2026-10-01T00:00:00Z") },
})

export default defineQuest({
  key: "admin-heart",
  title: "Get a ❤ from an admin",
  settings: settings.object({}),
  trigger: automatic({
    tracker: reactions,
    beneficiary: "messageAuthor",
    where: [
      { path: "reactorIsAdmin", equals: true },
      { path: "emoji", equals: "❤" },
    ],
  }),
  completion: ({ keyed }) => keyed(({ tracker }) => String(tracker.record.data.messageId)),
  rewards: [],
  async evaluate() {
    return decision.accept()
  },
})

This example pays a message's author once per message when an administrator reacts to it with a heart.

Tip with reactions, capped per giver and per receiver

A tip pays a message's author each time an admin reacts with ⚡ or ❤. Two daily limits keep it fair:

  • Per receiver. A member earns at most 20 tips a day, from everyone together.
  • Per giver. Each admin gives at most 10 tips a day, to everyone together. This holds whether or not the admin has linked Telegram: Domino counts the Telegram account that reacted.

After either limit is reached, the next tip that day pays nothing, and that reaction doesn't pay later either. A new day starts at midnight in your project calendar.

Each tip that pays is also announced in the group, replying to the tipped message: "@ana just received +10 XP by getting a tip from @sam". Pick the group in the quest's Settings.

import {
  automatic,
  budget,
  decision,
  defineQuest,
  defineTracker,
  points,
  settings,
  telegram,
} from "@domino-sdk/relay/authoring"

export const reactions = defineTracker({
  key: "telegram-tip-reactions",
  title: "Telegram reactions",
  source: telegram.reactions(),
  window: { start: Date.parse("2026-10-01T00:00:00Z") },
})

export default defineQuest({
  key: "admin-tip",
  title: "Get tipped by an admin",
  settings: settings.object({
    balance: settings.pointLedger({ label: "Point ledger" }),
    chat: settings.telegramChat({ label: "Telegram group" }),
  }),
  trigger: automatic({
    tracker: reactions,
    beneficiary: "messageAuthor",
    where: { path: "reactorIsAdmin", equals: true },
  }),
  // Every tip pays: one completion per reaction.
  completion: ({ keyed }) => keyed(({ tracker }) => tracker.record.id),
  budgets: [
    budget({ key: "tips-received", scope: "member", period: "day", limit: 20, cost: 1 }),
    budget({ key: "tips-given", scope: "author", period: "day", limit: 10, cost: 1 }),
  ],
  review: () => ({ enabled: false }),
  rewards: ({ settings }) => (settings.balance ? [points(settings.balance, 10)] : []),
  // Posted after the points are saved; none until a group is picked.
  announcements: ({ settings }) =>
    settings.chat
      ? [
          telegram.postMessage({
            chat: settings.chat,
            text: "{beneficiary} just received +{points} XP by getting a tip from {author}",
            replyToTriggeringMessage: true,
          }),
        ]
      : [],
  async evaluate({ tracker }) {
    return ["⚡", "❤"].includes(String(tracker.record.data.emoji))
      ? decision.accept()
      : decision.reject({ reason: "Tip with ⚡ or ❤." })
  },
})

Reward messages

Reward members for posting in your connected groups, such as a daily reward for joining the conversation or a streak of active days.

  • Linked accounts only. Domino records a message only when its poster has linked Telegram in the project.
  • People, not bots. Bots' messages, service messages such as joins and pins, and messages sent on behalf of a chat are never recorded.
  • Groups, not channels. A channel post is authored by the channel, so channel posts are never recorded.
  • No text. Domino records that a message was posted, never what it says.
  • Edits and deletes. An edited message is the same message. Telegram doesn't tell bots when a message is deleted, so a deleted message stays recorded.

The telegram-contribution snippet pays a member at most once per day for posting; install it as described in Your first quest. Days follow the project calendar. In the quest's settings, pick the point ledger and, optionally, one group; with no group picked, posts in any connected group count.

Announce tips in the group

A quest can post a message in one of your connected groups each time it pays, such as "@ana just received +10 XP by getting a ⚡ from @sam" when an admin tips a message. The message is what makes tipping visible to everyone in the group.

  1. Install or write the quest. Your developer adds the announcement to the quest; see below.
  2. Pick the group. In the quest's Settings, choose the group from your connected chats. The picker says when Domino's bot can't post in a chat.
  3. Publish. Publishing checks that the chat is connected in this environment and that the bot can post there.

How it behaves:

  • Points first. The message is posted after the points are saved. If Telegram is down or refuses the message, the member keeps the points.
  • It keeps trying. A failed post is retried with backoff for about a day. If Telegram refuses it for good, for example because the bot was removed, the member's Announcements in Console show why, and Retry delivery posts it once you've fixed the cause.
  • Paced, never dropped. Telegram lets a bot send about 20 messages a minute in one group, so Domino posts at most one message every 3 seconds per chat. A burst of tips waits in line, oldest first. If Telegram asks Domino to slow down, the whole chat waits.
  • People are mentioned. The member who earned is mentioned by their @username, or by a link to their Telegram account that shows their name. The same goes for whoever triggered it, such as the admin who tipped. A member with no Telegram account in the project is named by their Domino display name, or as "someone" while their profile is hidden.
  • Replies, if you want. The message can reply to the tipped message. A message quest that doesn't reply still posts in the forum topic the message was in.
  • Test stays in Test. A Test environment posts only in chats connected to Test, and never in a chat any project connected to Live. Publication refuses such a chat too.
  • Revoking. Revoking the completion cancels a message not yet posted. A message already posted stays in the chat, and restoring the completion doesn't post a cancelled one.

Operators

Console lists the member's Telegram account under Identities on the member's page, beside X and Discord. Reviewers unlink it, or relink one the member proved, with a reason, as they do the others. The member keeps their sign-in and can connect again. An imported or vouched Telegram account the member never used is released instead, so its owner can connect it. See operator identity changes.

On this page