Guía paso a paso para conectar tu backend a TronDealer. Registra tu negocio vía API, asigna wallets de depósito en 12 redes (EVM, TRON, Solana, Bitcoin, Litecoin, TON), verifica firmas HMAC X-Signature-256 y entiende el lifecycle de depósitos en USDT, USDC y monedas nativas.
Esta guía documenta la integración completa de TronDealer, la pasarela de pagos con stablecoins operada por QvaPay. Al terminar tendrás un backend capaz de aceptar USDT y USDC (y algunas stablecoins y monedas nativas más) en 12 redes, con detección automática de depósitos, notificaciones firmadas por webhook y sweep de fondos a tu destino de cobro.
Si vienes de otra pasarela (Coinbase Commerce, BitPay, NOWPayments), la API de TronDealer cubre el mismo flujo —registro, checkout, webhook, reconciliación— pero sin volatilidad de precio (los clientes pagan directamente en stablecoins) y con soporte nativo para mercados hispanos vía QvaPay.
ngrok, cloudflared o un túnel en dev).La API V2 está organizada en seis subsistemas. Cada uno tiene su propio prefijo de ruta, sus propias wallets de depósito y su propio umbral de confirmaciones; la forma de las peticiones y respuestas es la misma en todos.
| Subsistema | Prefijo de ruta | Redes | Activos aceptados | Confirmaciones por defecto |
|---|---|---|---|---|
| EVM | /api/v2/ | BSC, Ethereum, Polygon, Arbitrum, Base, Avalanche, Robinhood Chain | USDT + USDC en todas salvo Robinhood Chain (solo USDG); PYUSD en Ethereum; BNB, ETH y POL nativos en BSC, Ethereum y Polygon | 10 (BSC, ETH, Base, Avalanche), 20 (Polygon), 6 (Arbitrum, Robinhood) |
| TRON | /api/v2/tron/ | TRON | USDT (TRC20), TRX nativo | 19 |
| Solana | /api/v2/sol/ | Solana | USDT, USDC, USDG (SPL), SOL nativo | 1 (finalized) |
| Bitcoin | /api/v2/btc/ | Bitcoin | BTC | 2 |
| Litecoin | /api/v2/ltc/ | Litecoin |
Una misma dirección EVM es válida en las siete redes EVM a la vez: la asignas una sola vez y pasas network solo cuando una petición tiene que leer una cadena concreta. Los otros cinco subsistemas generan su propio formato de dirección.
Las monedas nativas se aceptan como depósito, pero el amount del webhook es el equivalente en USD en el momento de la detección, acompañado de amount_native y price_usd, para que tu contabilidad siga en dólares. Los importes nativos muy pequeños (menos de ~1 USD) se ignoran por completo. Los depósitos de stablecoin por debajo de 0.50 también.
El catálogo en vivo —cadenas, activos, direcciones de contrato, decimales y umbrales de confirmación— se sirve sin autenticación en GET /api/v2/networks. Úsalo en lugar de hardcodear.
Optimism se retiró el 2026-09-15: opt ya no es un valor de network aceptado (400). Si la integraste antes, quítala de tu checkout; los fondos que estaban en curso se barrieron con normalidad.
El ciclo de vida de un pago tiene cuatro estados. Todo lo que integres debe entender esta progresión:
webhook_secret.El registro es público y no requiere aprobación manual. register-public necesita un token Cloudflare Turnstile (CAPTCHA) obtenido desde el widget en el frontend. ¿Pruebas desde un backend sin navegador? Usa el formulario web en /onboard, o POST /api/v2/clients/register-open: mismo body sin turnstile_token, anónimo, limitado a un registro por IP y hora.
none (Turnstile token)Usa un label único por pedido (por ejemplo el ID de orden) al hacer POST /wallets/assign, y single_use: true si la wallet debe retirarse tras el pago. Esto te da una wallet dedicada por transacción y simplifica la reconciliación: el match es 1:1 entre pedido y dirección de destino.
data.tx_hash más el localizador que trae el payload (vout_index en Bitcoin/Litecoin, lt en TON), más to_address. Si un webhook se entrega dos veces (reintento) no acreditarás el pago dos veces.data.amount (USD). En monedas nativas guarda también amount_native y price_usd para la trazabilidad.x-api-key debe vivir solo en tu backend. Nunca la expongas en frontend ni en un bundle móvil. Todas las rutas clients/me/* también la aceptan, así que una app nativa puede hablar con la API directamente si la clave vive en el keychain del dispositivo.Con el flujo en marcha, lo que queda es endurecer la parte operativa: monitorear entregas de webhooks y sweeps fallidos desde el dashboard (pestaña Webhooks y pestañas por red), ajustar min_confirmations según tu tolerancia al riesgo, y declarar cualquier depósito perdido con wallets/check-hash en lugar de abrir un ticket.
La referencia completa de la V2 —cada ruta, petición y respuesta— está en la documentación interactiva y en la especificación OpenAPI. ¿Vendes con WordPress? El plugin de WooCommerce envuelve todo este flujo sin código.
| LTC |
| 6 |
| TON | /api/v2/ton/ | TON | USDT (Jetton), GRAM nativo | 1 |
curl -X POST https://www.trondealer.com/api/v2/clients/register-public \
-H "Content-Type: application/json" \
-d '{
"name": "Mi Tienda",
"webhook_url": "https://mi-backend.com/webhooks/trondealer",
"webhook_secret": "un-secreto-largo-y-aleatorio",
"min_confirmations": 10,
"payout_method": "wallet",
"sweep_wallet_evm": "0xABC...123",
"sweep_wallet_tron": "TXyz...789",
"turnstile_token": "..."
}'La respuesta incluye client.api_key con prefijo td_ seguido de 64 caracteres hex. Guárdalo en tu vault de secretos: esta cabecera es la credencial que autentica todas las llamadas posteriores y no podrás recuperarla.
Al registrar defines cómo recibes los fondos después del sweep: wallet (tus propias direcciones, una por cada subsistema que vayas a usar: sweep_wallet_evm, sweep_wallet_tron, sweep_wallet_sol, sweep_wallet_btc, sweep_wallet_ltc, sweep_wallet_ton; hace falta al menos una), qvapay (qvapay_account: usuario o email) o zelle (zelle_contact: email o teléfono). Todo se puede cambiar después con PATCH /api/v2/clients/me o desde la pestaña Ajustes del dashboard. Los cobros a wallet llevan una comisión del 0.4% (+ $0.30 fijos por debajo de $10), descontada on-chain antes del reenvío; ver Precios.
Cada cliente genera wallets bajo demanda. En la ruta EVM la misma dirección sirve en BSC, Ethereum, Polygon, Arbitrum, Base, Avalanche y Robinhood Chain; no hace falta network para asignarla. Para el resto de redes llama a la ruta del subsistema: /api/v2/tron/wallets/assign, /api/v2/sol/wallets/assign, /api/v2/btc/wallets/assign, /api/v2/ltc/wallets/assign, /api/v2/ton/wallets/assign — mismo body, dirección nativa de esa cadena en la respuesta.
x-api-key: td_...curl -X POST https://www.trondealer.com/api/v2/wallets/assign \
-H "x-api-key: td_..." \
-H "Content-Type: application/json" \
-d '{ "label": "Pedido #A-1024", "single_use": true }'Si pasas el mismo label dos veces, TronDealer devuelve la wallet activa existente (200 OK). Trata el label como tu clave de idempotencia: el ID de pedido, el user ID, lo que encaje.
single_use: true marca la wallet como inactiva tras su primer sweep exitoso, así sale del conjunto de escaneo. Úsalo para checkouts de una wallet por factura; déjalo apagado para una wallet que un cliente recurrente pagará varias veces. Las wallets sin actividad durante 120 días se desactivan automáticamente, y puedes activar o desactivar cualquiera con PATCH /api/v2/clients/me/wallets/{id} (o el equivalente con prefijo de subsistema) y { "status": "active" | "inactive" }. Cada cliente puede tener hasta 100 wallets activas por subsistema.
La clave privada se almacena cifrada en nuestra base de datos y nunca se expone por API. TronDealer orquesta todas las operaciones on-chain (sweep, gas funding) por ti.
Antes de marcar un pedido como pagado puedes verificar el balance en vivo de una wallet. En la ruta EVM cada petición lee una sola cadena: pasa network; si lo omites se usa eth. Los demás subsistemas solo necesitan la dirección.
x-api-key: td_...curl -X POST https://www.trondealer.com/api/v2/wallets/balance \
-H "x-api-key: td_..." \
-H "Content-Type: application/json" \
-d '{ "network": "bsc", "address": "0xABC...123" }'Respuesta típica:
{
"success": true,
"wallet": { "address": "0xABC...123", "label": "Pedido #A-1024", "status": "active" },
"balances": {
"bsc": { "BNB": "0.012", "USDT": "50.0", "USDC": "0.0" }
}
}Los balances son lecturas on-chain en vivo y tienen límite de una petición por minuto por cliente y subsistema (429 con code: "RATE_LIMITED" y cabecera Retry-After). En la práctica no necesitas pollearlo: el webhook de confirmación (paso 5) llega automáticamente cuando entra un depósito. Úsalo para reconciliación manual o dashboards internos.
Para mostrar el historial de un cliente o reconciliar depósitos contra tu ERP, lista las transacciones asociadas a sus wallets.
x-api-key: td_...const res = await fetch('https://www.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()Cada transacción incluye el identificador natural de su cadena (tx_hash en EVM, tx_id en TRON, tx_signature en Solana, tx_hash + vout_index en Bitcoin y Litecoin, tx_hash + lt en TON), from_address, to_address, asset, amount decodificado, confirmations actuales, status, network (EVM), y timestamps.
Esta es la parte crítica de cualquier pasarela: garantizar que los webhooks que recibes son auténticos y no fueron inyectados por un tercero con acceso a tu URL.
Si registraste un webhook_secret, cada POST incluye un header X-Signature-256 con la firma HMAC-SHA256 del body completo. Debes:
HMAC-SHA256(secret, raw_body) en hex.El body tiene un sobre fijo: event (transaction.incoming, transaction.confirmed, transaction.swept, opcionalmente transaction.confirmation_update), timestamp y data con el depósito: tx_hash, from_address, to_address, asset, amount, amount_native, price_usd, confirmations, wallet_label, network, más el localizador de la cadena (block_number, slot, vout_index, lt). En stablecoins amount coincide con amount_native; en monedas nativas amount es el equivalente en USD.
Una comparación string igualdad clásica es vulnerable a timing attacks. Usa crypto.timingSafeEqual en Node, hmac.compare_digest en Python o hash_equals en PHP. Es un detalle pequeño pero crítico.
Si tu webhook devuelve un código ≠ 2xx, TronDealer reintenta automáticamente con backoff exponencial. Responde 200 lo antes posible y procesa el evento de forma asíncrona (cola) si tu lógica es pesada.
Cada depósito atraviesa estos estados. Diseña tu lógica de negocio alrededor de confirmed (o notified, que implica confirmado), nunca de detected.
| Estado | Disparador | Acción recomendada |
|---|---|---|
detected | El scanner ve el depósito | No marcar pedido como pagado aún |
confirmed | ≥ confirmaciones mínimas de esa red | Candidato a liberar producto/servicio |
notified | Webhook entregado con 2xx | Tu sistema ya confirmó recepción |
swept | Fondos reenviados a tu destino de cobro | Cobro consolidado, listo para contabilidad |
Cada paso tiene su evento de webhook: transaction.incoming se dispara en detected (se reintenta hasta entregarse), transaction.confirmed cuando se alcanza el umbral (5 intentos con backoff — este es el evento para acreditar saldos), y transaction.swept llega best-effort tras el sweep.
Mientras un depósito sigue pendiente también puedes recibir eventos transaction.confirmation_update cada vez que el conteo de confirmaciones avanza — mismo payload que transaction.incoming más un campo min_confirmations, para pintar una barra de progreso tipo "6 de 10 confirmaciones". Está apagado por defecto: se activa con PATCH /api/v2/clients/me y {"notify_confirmation_updates": true}. Disponible en las redes EVM, Bitcoin y Litecoin. La entrega es fire-and-forget (sin reintentos, orden no garantizado respecto a transaction.confirmed) — trátalo como cosmético, muestra el máximo conteo visto, y nunca acredites saldo desde este evento.
Los umbrales de confirmación por defecto son los de la tabla de redes de arriba. En las redes EVM tu min_confirmations de cliente sobrescribe ese valor: súbelo a 20–30 si manejas montos altos, o bájalo a 6 si priorizas UX. TRON (19), Solana, Bitcoin, Litecoin y TON usan sus propios umbrales fijos. El valor se define al registrarte, con PATCH /api/v2/clients/me o desde la pestaña Ajustes del dashboard.
import crypto from 'node:crypto'
import express from 'express'
const app = express()
const SECRET = process.env.WEBHOOK_SECRET
// IMPORTANTE: body raw, no 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, data } = JSON.parse(req.body.toString())
// event: 'transaction.confirmed' | ...
// data.tx_hash, data.amount (USD), data.asset, data.network, data.wallet_label
if (event === 'transaction.confirmed') console.log('Pago confirmado:', data)
res.status(200).send('ok')
}
)