Payments

Telegram communities with affiliates

Sell access to a Telegram channel or group for USDT, open the checkout as a Mini-App inside your bot, and pay each affiliate their share automatically from the same payment.

Last updated Nov 1, 2026

A paid Telegram community can take USDT or USDC with Aliquot and give every affiliate who brought a member a fixed percentage of that member's payment. The bot creates an invoice that names the affiliate in splits, opens the hosted checkout as a Telegram Mini-App, and sends an invite link when the invoice.paid webhook arrives. The affiliate's share is credited to their own balance the moment the payment is confirmed, and you pay it out to their wallet with one API call. No spreadsheets, no manual commission run.

This guide uses Sandbox (BSC_TESTNET, USDT) and plain fetch calls to the Telegram Bot API, so it works with any bot library. You need an Aliquot API key (see Quickstart), a bot token from @BotFather, and the bot added as an administrator of the private channel or group, with the right to invite users.

How the money splits

For a 20.00 USD membership with a 20 % affiliate share and the default fee_payer CUSTOMER:

PartAmount
Buyer sends (total)20.00 + processing fee
Affiliate share (20 %)4.00
Your platform account (seller net)16.00
Aliquot processing feeby monthly volume, see Processing fee

Every part is a percentage of the base price, booked on the double-entry ledger when the invoice is paid. With fee_payer MERCHANT the buyer sends exactly 20.00 and the fee comes out of your share instead. The full arithmetic is in Ledger and splits.

An affiliate is a PARTNER account. Create one per affiliate (permission crypto-accounts:create), with a reference you can derive from your own data, such as their Telegram user ID:

curl -X POST https://api.aliquot.cc/api/v1/crypto/accounts \
  -H "Authorization: Bearer $ALIQUOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "PARTNER", "reference": "aff-5550001", "name": "Ana (affiliate)", "settlement_address": "0x7a3B9c1D5e2F4a6B8c0D1e3F5a7B9c2D4e6F8a0B" }'

References are 2–63 characters of lowercase letters, digits, _ and -, and unique in your organization; a duplicate answers 409, so creating the account again is safe to treat as "already exists".

Give the affiliate a Telegram deep link that carries their reference: https://t.me/<your_bot>?start=aff-5550001. When someone opens it, Telegram sends your bot the message /start aff-5550001. Store that reference against the new member's Telegram user ID in your database; it is who gets the share when that member pays.

2. Create the invoice with the affiliate's split

When the member asks to join, create an invoice. Put their Telegram user ID in merchant_order_id: invoice webhooks carry it back, so you know whom to invite.

const BOT = `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}`;

async function createMembershipInvoice(telegramUserId, affiliateRef) {
  const orderId = `tg-${telegramUserId}-${Date.now()}`;
  const res = await fetch('https://api.aliquot.cc/api/v1/crypto/invoices', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.ALIQUOT_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': orderId,
    },
    body: JSON.stringify({
      amount: '20.00',
      title: 'VIP channel · 30 days',
      network: 'BSC_TESTNET',
      token: 'USDT',
      merchant_order_id: orderId,
      ...(affiliateRef ? { splits: [{ account: affiliateRef, percent: 20 }] } : {}),
    }),
  });
  const invoice = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${invoice.code}: ${invoice.message}`);
  return invoice; // invoice.pay_url is the hosted checkout
}
  • Without an affiliate, leave splits out: your platform account keeps the whole net.
  • An unknown account reference in splits is a 422 on that field. Create the partner account first.
  • Live is the same request on BSC_MAINNET with a Live key. See Networks and tokens for every pair.

3. Open the checkout as a Mini-App

Add ?tg=1 to pay_url and send it as a web_app button. Telegram opens the hosted checkout inside the chat: the same amount, QR code, wallet button and live confirmation status as /pay/:id, with the main action on Telegram's own MainButton.

async function sendCheckout(chatId, invoice) {
  await fetch(`${BOT}/sendMessage`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      chat_id: chatId,
      text: `VIP channel · 30 days: ${invoice.total} ${invoice.token}`,
      reply_markup: {
        inline_keyboard: [[{ text: 'Pay with USDT', web_app: { url: `${invoice.pay_url}?tg=1` } }]],
      },
    }),
  });
}

Telegram only allows web_app buttons in a private chat between the user and the bot. In a group, send a regular url button with pay_url instead; it opens the same checkout in the browser.

The Mini-App shows the buyer's Telegram name only when it can verify the signed session; otherwise it shows "Telegram session not verified". The payment works the same either way. Identify the buyer by your merchant_order_id, never by what the page displays.

4. Send the invite when invoice.paid arrives

Subscribe an endpoint to invoice.paid (Developers → Webhooks) and verify the signature exactly as in the Quickstart receiver. Then read the Telegram user ID back from merchant_order_id and create a single-use invite link:

async function onInvoicePaid(event) {
  const [, telegramUserId] = event.data.merchant_order_id.split('-');
  const invite = await fetch(`${BOT}/createChatInviteLink`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      chat_id: process.env.VIP_CHAT_ID,
      member_limit: 1,
      expire_date: Math.floor(Date.now() / 1000) + 24 * 60 * 60,
    }),
  }).then((r) => r.json());

  await fetch(`${BOT}/sendMessage`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ chat_id: telegramUserId, text: `Paid. Your invite: ${invite.result.invite_link}` }),
  });
}

Make it idempotent on event.data.invoice_id: a delivery your endpoint does not answer with 2xx is retried, so the same event can arrive more than once (see Webhooks). invoice.paid also fires for OVERPAID invoices; the overpaid difference is held for the buyer to claim back (see Refunds).

Fulfil only from the webhook. A payment counts once it reaches the confirmation threshold of its network; see Confirmations.

5. Pay affiliates

Each affiliate's share sits in their partner balance. Read it with GET /api/v1/crypto/balances and send it to their wallet with a payout:

curl -X POST https://api.aliquot.cc/api/v1/crypto/payouts \
  -H "Authorization: Bearer $ALIQUOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-2026-11-aff-5550001" \
  -d '{
    "account": "aff-5550001",
    "network": "BSC_TESTNET",
    "token": "USDT",
    "amount": "40.00",
    "destination_address": "0x7a3B9c1D5e2F4a6B8c0D1e3F5a7B9c2D4e6F8a0B"
  }'

The key needs crypto-payouts:create, which only a user with two-factor authentication can grant. Each payout has a fee that comes out of amount, and a maximum per payout; both are in Payouts. You can also pay out from the dashboard (Payouts → New payout) with a code from your authenticator app.

Next

  • Ledger and splits: markup, several partners on one invoice, and the ledger postings.
  • Payment links: a fixed-price page you can pin in a channel, when you don't need per-member attribution.
  • Webhooks: every event, retries and secret rotation.