TON is not Ethereum with a different RPC. The asynchronous transaction model and tree-like message structure break developer intuition. When a user sends native TON to your address, it's a single transaction. When they send Jetton (USDT on TON), it's a chain of three: transfer → internal message → notification. A monitoring error leads to lost payments and headache with refunds.
Recently, a project with 5000 daily Jetton payments approached us. After auditing their system, we found they weren't accounting for bounced transactions, causing 2% of payments to be credited erroneously. We rebuilt the architecture with unique addresses and Gasless relays, cutting losses to zero. We set up payment acceptance turnkey: contact us, and we'll assess your project and choose the optimal architecture in one day.
How to Accept Native TON and Jetton
Native TON
Generate a unique address or use a single address with a comment (memo) for identification. Monitor via TON Center API or TonAPI:
import { TonClient } from '@ton/ton'; import { Address } from '@ton/core'; const client = new TonClient({ endpoint: 'https://toncenter.com/api/v2/jsonRPC', apiKey: process.env.TONCENTER_API_KEY, }); async function checkIncomingTransactions( address: string, lastLt: string // last known logical time ) { const addr = Address.parse(address); const transactions = await client.getTransactions(addr, { limit: 20, lt: lastLt, archival: false, }); for (const tx of transactions) { // Only incoming, not bounce if (tx.inMessage && tx.inMessage.info.type === 'internal') { const info = tx.inMessage.info; const value = info.value.coins; // in nanoTON const comment = tx.inMessage.body; // text comment // Match comment with our payment ID console.log(`Received: ${value} nanoTON, comment: ${comment}`); } } } Important: check the bounce flag and bounced flag. A bounced transaction means a return—do not count it.
Jetton (USDT, USDC, NOT)
Jetton Transfer is more complex: the user sends a message to their JettonWallet, which sends an internal message to the recipient's contract, which then sends a transfer_notification to the recipient's address. In forward_ton_amount, we include the fee for the notification; in forward_payload, we include the payment ID:
transfer_notification#7362d09c query_id: uint64 amount: coins // amount of Jetton sender: MsgAddress // sender's address forward_payload: ^Cell // our custom payload (payment ID) Monitor not the main address, but the JettonWallet of our address:
// Get our JettonWallet address for USDT async function getJettonWalletAddress( ownerAddress: string, jettonMasterAddress: string ): Promise<string> { const master = client.open( JettonMaster.create(Address.parse(jettonMasterAddress)) ); const walletAddr = await master.getWalletAddress( Address.parse(ownerAddress) ); return walletAddr.toString(); } // USDT on TON mainnet const USDT_MASTER = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'; What to Choose: Unique Addresses or Comment?
| Characteristic | Comment (memo) | Unique Address |
|---|---|---|
| Implementation complexity | Low | Medium (HD wallet) |
| User errors | 1-3% forget comment | 0% |
| Fund sweeping | Not required | Required sweep |
| Monitoring | One address | Many addresses |
| Recommendation | Up to 100 payments/day | From 1000 payments/day |
Comment/Memo Identification
One address, user specifies comment (payment ID). Simple, but requires UX—explain the need for a comment. Error = lost payment (needs manual reconciliation).
Unique Address per Payment
Generate HD wallet (BIP39 + non-standard derivation). Each order gets a separate address. No comments, no errors, simple monitoring:
import { mnemonicToPrivateKey } from '@ton/crypto'; import { WalletContractV4 } from '@ton/ton'; async function derivePaymentAddress( masterMnemonic: string[], orderIndex: number ): Promise<string> { const keyPair = await mnemonicToPrivateKey(masterMnemonic); const wallet = WalletContractV4.create({ publicKey: keyPair.publicKey, workchain: 0, walletId: 698983191 + orderIndex, // unique subwalletId }); return wallet.address.toString({ bounceable: false }); } Downside: need to sweep funds to a main address.
Polling or Webhook?
| Method | Latency | Load | Complexity |
|---|---|---|---|
| Polling (TON Center) | ~5-30 sec | Medium | Low |
| Webhook (TON Center) | ~1-2 sec | Low | Medium |
| WebSocket (TonAPI) | ~0.5 sec | Low | High |
| Own node | ~0 sec | Very high | Very high |
For production, use TonAPI + WebSocket with polling fallback. A self-hosted node is justified for millions of transactions per day. Webhook is 10x faster than polling.
Gasless and Bounce: Common Problems
Gasless
Gasless allows users to pay without a TON balance for fees. This is critical for Jetton payments: to send USDT, you need TON for gas. The service covers the fee via a relay. Set up a relay via TON Connect or a relay contract. Gasless increases conversion by 15-30% in mobile apps.
Bounce
If you don't filter bounced transactions, you may credit a payment that never arrived. In TON, bounces are normal: the recipient contract may reject the message. Check the bounced flag in the message body. For Jetton, also track transfer_notification—its absence is also a sign of failure.
Steps to Set Up TON Payment Acceptance
- Analysis—assess load, asset types (TON, Jetton), choose architecture.
- Choose identification method—comment or unique addresses.
- Develop monitoring—integrate with TON Center / TonAPI, handle webhook/WebSocket.
- Integrate with backend—map transactions to orders, handle errors.
- Test on testnet—use a bot to distribute test TON and Sandbox from Blueprint.
- Deploy—set up production environment, monitoring, and alerts.
What's Included in the Work
- Architecture documentation—detailed design of payment flow.
- Access to test environment—testnet endpoints and credentials.
- Monitoring setup—alerts for bounced transactions and failures.
- Training session—one-hour walkthrough for your team.
- Support for 30 days after deployment.
Our Company Metrics
- 5+ years in blockchain development.
- 50+ projects delivered, including 10+ TON integrations.
- 99.9% uptime for payment systems.
Timelines and Cost
Basic setup takes 2–4 weeks. Costs start from $5,000 for a simple setup with one asset and comment-based identification. Gasless relay and multi-asset support increase the budget. Get a free project assessment—contact us today.
Common Mistakes in TON Payment Acceptance
- Ignoring bounced transactions—crediting failed payments.
- Monitoring the main address instead of JettonWallet—missing Jetton payments.
- Using only polling without fallback—losing transactions under high load.
- Not testing on testnet—production errors.
- Not accounting for asynchronicity—trying to wait synchronously for a contract response.
Testing and Deployment
Testnet: use a bot to distribute test TON. API endpoint https://testnet.toncenter.com/api/v2/jsonRPC. For local development, use Sandbox from Blueprint: a TVM emulator without network. The asynchronous message model requires special testing. Order TON payment acceptance setup to eliminate monitoring errors and automate accounting.
References: TON Documentation, TON Center API, TonAPI, Blueprint Sandbox







