How to Integrate TON Connect into a Telegram Mini App
Advanced4 minInnovialabs editorial team
In short
Connect wallets with @tonconnect/ui and a manifest, prove address ownership with ton_proof verified on the server, send transactions via sendTransaction and mark orders paid only after checking the blockchain — and sort out Telegram rules and local regulation before launch.
How it works
TON Connect is the protocol a Mini App uses to talk to the user's wallet (Telegram Wallet, Tonkeeper and others). Your app never gets the keys — it only asks the wallet to sign data or a transaction.
The full flow has four parts:
- Manifest — a JSON file describing your app.
- Wallet connection — the user picks a wallet and approves.
- ton_proof — cryptographic proof that the address really belongs to the user. Verified on the backend.
- Transactions — the wallet signs and sends; the server checks the result on-chain.
Step 1. Manifest and connection
Host tonconnect-manifest.json at a public HTTPS URL:
{
"url": "https://example.com",
"name": "Example Shop",
"iconUrl": "https://example.com/icon-180.png",
"termsOfUseUrl": "https://example.com/terms",
"privacyPolicyUrl": "https://example.com/privacy"
}
In a React app, use @tonconnect/ui-react:
import { TonConnectUIProvider, TonConnectButton } from "@tonconnect/ui-react";
export function App() {
return (
<TonConnectUIProvider
manifestUrl="https://example.com/tonconnect-manifest.json"
actionsConfiguration={{ twaReturnUrl: "https://t.me/your_bot/app" }}
>
<TonConnectButton />
</TonConnectUIProvider>
);
}
twaReturnUrl brings the user back to the Mini App after approving in an external wallet. The useTonWallet() and useTonAddress() hooks expose the current connection.
Step 2. ton_proof: proving address ownership
The address from useTonAddress() is client-side data. It is as easy to fake as initDataUnsafe. If you use the address for login, account linking or crediting anything, you need ton_proof.
The flow:
- The backend generates a random payload (nonce) with a short lifetime and stores it.
- The frontend passes it into the connection request.
- The wallet signs a message containing your domain, a timestamp, the address and the payload.
- The frontend sends the signature to the backend.
tonConnectUI.setConnectRequestParameters({ state: "loading" });
const payload = await fetch("/api/ton-proof/payload").then((r) => r.text());
tonConnectUI.setConnectRequestParameters({ state: "ready", value: { tonProof: payload } });
tonConnectUI.onStatusChange((wallet) => {
const item = wallet?.connectItems?.tonProof;
if (wallet && item && "proof" in item) {
fetch("/api/ton-proof/check", {
method: "POST",
body: JSON.stringify({ account: wallet.account, proof: item.proof }),
});
}
});
What the backend checks:
- the payload was issued by you, has not expired and has not been used;
- the domain in the proof matches yours;
- the timestamp is fresh;
- the public key comes from
walletStateInitor a query to the wallet contract, and the claimed address really derives from it; - the Ed25519 signature is valid for the message built per the TON Connect specification.
Building the signed message is where byte-order mistakes happen. Rely on the official TON Connect backend examples rather than a hand-rolled implementation. After verification, issue your own session token.
Step 3. Sending a transaction
import { beginCell, toNano } from "@ton/core";
const comment = beginCell().storeUint(0, 32).storeStringTail(`order:${orderId}`).endCell();
await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 300,
messages: [
{
address: MERCHANT_ADDRESS,
amount: toNano("1.5").toString(),
payload: comment.toBoc().toString("base64"),
},
],
});
amountis in nanotons, as a string.- A comment with the order number helps match the payment to the order.
validUntillimits how long the user has to approve the transaction.
The key rule: a successful sendTransaction response means the wallet sent the message, not that the money arrived. The backend marks the order paid after finding the incoming transaction to your address through an indexer or blockchain API and matching the amount, address and comment. Processing must be idempotent: one transaction, one order.
Rules and regulation
Wiring up TON is technically simple. The legal side is harder.
- Telegram rules. For digital goods and services inside Mini Apps, Telegram requires payment in Stars. Crypto features in the Mini Apps ecosystem are built around TON and TON Connect. Check the current rules before launch.
- Local law. In many countries, including Uzbekistan, crypto assets are regulated separately and services involving them require a license. Accepting crypto as payment may be restricted.
- KYC and AML. Depending on your business model, you may need customer identification and source-of-funds checks.
- Taxes and accounting. You need to know how to book incoming payments and at what rate.
- Irreversibility. A blockchain transaction cannot be reversed — design refunds as a separate process.
- Volatility. TON prices move: lock the rate for a short window or use stablecoins where allowed.
Get advice from a lawyer for your jurisdiction and model before development.