Integrate TronDealer in 6 Steps: USDT Payment API, HMAC Webhooks and Auto-Sweep

Step-by-step guide to connect your backend to TronDealer. Register your business via API, assign multi-chain wallets, validate HMAC X-Signature-256 and understand the deposit lifecycle for USDT and USDC.

12 min readUpdated: April 20, 2026Leer en español

This guide documents the full integration of TronDealer, the stablecoin payment gateway operated by QvaPay. By the end you will have a backend able to accept USDT and USDC on BSC, Ethereum and Polygon, with automatic deposit detection, signed webhook notifications and fund sweeping to a destination wallet.

Coming from another processor (Coinbase Commerce, BitPay, NOWPayments)? TronDealer's API covers the same flow — registration, checkout, webhook, reconciliation — but with zero price volatility (customers pay directly in stablecoins) and first-class support for Hispanic markets via QvaPay.

Prerequisites
  • A backend that can make HTTPS requests and parse JSON.
  • A public HTTPS URL to receive webhooks (use ngrok, cloudflared or any tunnel in dev).
  • Basic understanding of HMAC-SHA256 signature verification.

Flow overview#

A payment moves through four states. Whatever you build should understand this progression:

detectedconfirmednotifiedswept
  1. detected — TronDealer sees the deposit on the relevant network (BSC, ETH or Polygon).
  2. confirmed — The transaction accumulates enough on-chain confirmations (default 15 on BSC).
  3. notified — A webhook POST fires to your configured URL, signed with HMAC if you set a webhook_secret.
  4. swept — Funds are automatically swept to your main wallet or QvaPay account.

The 6 steps#

  1. 1

    1. Register your business via API

    Registration is public and requires no manual approval. You need a Cloudflare Turnstile CAPTCHA token obtained from the frontend widget, but if you are testing from backend you can register through the web form at /onboard and then authenticate.

    POSThttps://trondealer.com/api/v2/clients/register-public
    Authnone (Turnstile token)
    curl -X POST https://trondealer.com/api/v2/clients/register-public \
      -H "Content-Type: application/json" \
      -d '{
        "name": "My Shop",
        "webhook_url": "https://my-backend.com/webhooks/trondealer",
        "webhook_secret": "a-long-random-secret",
        "min_confirmations": 15,
        "sweep_wallet": "0xABC...123",
        "payout_method": "wallet",
        "turnstile_token": "..."
      }'

    The response includes client.api_key prefixed with td_ followed by 64 hex chars. Store it in your secret vault: this header authenticates every subsequent call and cannot be recovered.

    Pick your payout_method carefully

    At registration you decide how to receive funds after sweep: wallet (your personal EVM address), qvapay (QvaPay account by username or email) or zelle (email or phone). Changing it later requires editing settings in the dashboard.

  2. 2

    2. Assign a multi-chain wallet

    Clients generate wallets on demand. The same address works on BSC, Ethereum and Polygon (the three V2-supported networks).

    POSThttps://trondealer.com/api/v2/wallets/assign
    Authx-api-key: td_...
    curl -X POST https://trondealer.com/api/v2/wallets/assign \
      -H "x-api-key: td_..." \
      -H "Content-Type: application/json" \
      -d '{ "label": "Order #A-1024" }'

    If you pass the same label twice, TronDealer returns the existing wallet (200 OK). Treat label as your idempotency key: order ID, user ID, whatever fits.

    The private key is stored encrypted in our database and never exposed via API. TronDealer orchestrates every on-chain operation (sweep, gas funding) for you.

  3. 3

    3. Query the on-chain balance

    Before marking an order as paid you can verify live balances for a wallet. This endpoint queries the USDT and USDC contracts across the three networks and returns decoded balances.

    POSThttps://trondealer.com/api/v2/wallets/balance
    Authx-api-key: td_...
    balance.sh
    curl -X POST https://trondealer.com/api/v2/wallets/balance \
      -H "x-api-key: td_..." \
      -H "Content-Type: application/json" \
      -d '{ "address": "0xABC...123" }'

    Typical response:

    balance-response.json
    {
      "success": true,
      "balances": {
        "bsc":  { "native": "0.012", "usdt": "50.0", "usdc": "0.0" },
        "eth":  { "native": "0.0", "usdt": "0.0", "usdc": "25.5" },
        "pol":  { "native": "0.0", "usdt": "0.0", "usdc": "0.0" }
      }
    }

    In most cases you don't need to poll it: the confirmation webhook (step 5) fires automatically when a deposit is detected. Use this for manual reconciliation or internal dashboards.

  4. 4

    4. List transactions with filters and pagination

    To show a customer's history or reconcile deposits against your ERP, list transactions tied to their wallets.

    POSThttps://trondealer.com/api/v2/wallets/transactions
    Authx-api-key: td_...
    list-transactions.js
    const res = await fetch('https://trondealer.com/api/v2/wallets/transactions', {
      method: 'POST',
      headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        address: '0xABC...123',
        status: 'confirmed', // detected | confirmed | notified | swept
        limit: 20,
        offset: 0,
      }),
    })
    const { transactions, total } = await res.json()

    Each transaction carries: tx_hash, block_number, from_address, to_address, asset (USDT/USDC), decoded amount, current confirmations, status, network, webhook_sent, and timestamps.

  5. 5

    5. Configure and verify the HMAC webhook

    This is the critical part of any payment gateway integration: making sure the webhooks you receive are authentic and not injected by a third party with knowledge of your URL.

    If you registered a webhook_secret, every POST includes an X-Signature-256 header with the HMAC-SHA256 signature of the full body. You must:

    1. Read the raw body (before JSON-parsing).
    2. Compute HMAC-SHA256(secret, raw_body) as hex.
    3. Compare against the header value using a constant-time comparison.
    import crypto from 'node:crypto'
    import express from 'express'
     
    const app = express()
    const SECRET = process.env.WEBHOOK_SECRET
     
    // IMPORTANT: raw body, not json()
    app.post(
      '/webhooks/trondealer',
      express.raw({ type: 'application/json' }),
      (req, res) => {
        const sig = req.headers['x-signature-256']
        const expected = crypto
          .createHmac('sha256', SECRET)
          .update(req.body)
          .digest('hex')
     
        if (!sig || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
          return res.status(401).send('invalid signature')
        }
     
        const event = JSON.parse(req.body.toString())
        // event.tx_hash, event.amount, event.asset, event.network, event.status
        console.log('Payment confirmed:', event)
        res.status(200).send('ok')
      }
    )
    Never use plain `==` comparison

    A classic string-equality check is vulnerable to timing attacks. Use crypto.timingSafeEqual in Node, hmac.compare_digest in Python, or hash_equals in PHP. Small detail, big deal.

    Retries

    If your webhook returns a non-2xx status, TronDealer retries with exponential backoff. Respond 200 as fast as possible and process the event asynchronously (queue) if your logic is heavy.

  6. 6

    6. Understand the end-to-end lifecycle

    Each deposit walks through these states. Build your business logic around confirmed (or notified, which implies confirmed) — never around detected.

    StateTriggerRecommended action
    detectedScanner sees the Transfer logDo not mark order as paid yet
    confirmed≥ min_confirmations blocksCandidate to release product/service
    notifiedWebhook delivered with 2xxYour system acknowledged receipt
    sweptFunds moved to main walletConsolidated, ready for accounting

    Default confirmations are 15 (BSC, fast). Bump it to 20–30 if you handle larger amounts, or lower it to 6–10 for better UX. Value is per-client and set at registration or via /dashboard/settings.

Reconciliation and best practices#

One order, one wallet

Use a unique label per order (e.g. your order ID) when calling POST /wallets/assign. You get a dedicated wallet per transaction and 1:1 reconciliation: order-to-destination-address match is trivial.

  • Idempotency: use tx_hash + log_index as a unique key in your database. If a webhook is delivered twice (retry) you won't double-credit.
  • Timeouts: don't flag an order as "failed" just because the wallet is empty after 60s. BSC deposits take ~15s, ETH several minutes.
  • Logs: persist the raw webhook body alongside the signature. It will save you hours when debugging.
  • Security: the x-api-key must live only on your backend. Never ship it in a frontend or mobile bundle.

FAQ#

Next steps#

With the flow live, the rest is operational hardening: monitor webhook deliveries at /dashboard/webhooks, set up alerts for failed sweeps at /dashboard/sweeps, and tune min_confirmations against your risk tolerance.

If you need endpoints not yet covered by V2 (TRON, Solana, Bitcoin), the Swagger docs list the V1 endpoints by chain.

Watch the video walkthrough#

Full walkthrough of the TronDealer system on video.