v1.2.0 OAS 3.0.3

PixUSDT v1

API REST para gerar cobranças PIX e receber USDT na sua carteira automaticamente. Construída sobre HTTPS com auth Bearer e webhooks assinados via HMAC-SHA256.

Base URL: https://pixusdt.org/v1 JSON UTF-8

Introdução

O PixUSDT é uma plataforma de pagamentos que recebe PIX em BRL e envia USDT (TRON / Polygon / BSC) automaticamente para a carteira configurada do lojista. Esta API permite integrar o PixUSDT ao seu checkout, sistema de gestão ou painel administrativo.

Fluxo típico: seu sistema chama POST /v1/charges com o valor → PixUSDT retorna QR code PIX e copia-e-cola → o pagador paga → PixUSDT recebe webhook do PSP, confirma o pagamento, e envia POST assinado para a sua URL configurada → seu sistema marca o pedido como pago.

Autenticação

Todas as requisições à API /v1/* exigem autenticação via API Key. Gere a sua em Dashboard → API Keys & Webhooks. As chaves começam com pk_live_ e devem ser tratadas como senha.

Header padrão (recomendado)

httpAuthorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Alternativa

httpX-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
⚠ Nunca exponha a API key no front-end. Sempre faça as chamadas a partir do seu backend. Chaves comprometidas devem ser revogadas imediatamente no dashboard.

Health check

Endpoint público pra monitoramento (UptimeRobot, statuspage etc):

GET/health
json{
  "status": "ok",
  "checks": {
    "database": "ok",
    "psp_mode": "pushinpay"
  },
  "uptime_seconds": 3600,
  "response_time_ms": 2,
  "version": "1.1.0",
  "timestamp": "2026-05-06T12:00:00Z"
}

Retorna HTTP 200 quando tudo OK, 503 se algum serviço crítico está degradado.

Carteira & Rede USDT

O PixUSDT é não-custodial: nós convertemos PIX em USDT e enviamos diretamente para a carteira que você cadastra no painel. Não custodiamos cripto — assim que o PIX é confirmado, o operador da plataforma envia USDT direto pro seu wallet, on-chain, com TX hash registrado e auditável.

Redes suportadas

RedeTokenTaxa de rede aprox.Tempo de confirmação
TRONUSDT (TRC-20)~$1 USD~30 segundos
POLYGONUSDT (Polygon PoS)~$0.05 USD~5 segundos
BSCUSDT (BEP-20)~$0.30 USD~3 segundos

Cadastre seu endereço e a rede em Dashboard → Meu Perfil → Carteira USDT. O endereço é validado automaticamente conforme o formato da rede escolhida. A consulta da rede atual está disponível em GET /v1/account:

json{
  "id": 42,
  "name": "Loja Exemplo",
  "email": "loja@exemplo.com",
  "role": "admin",
  "usdt_wallet": {
    "address": "TXyz...AbCd",
    "network": "TRON",
    "configured": true
  }
}
⚠ Verifique antes de operar: chame GET /v1/account no setup da sua integração e confirme que usdt_wallet.configured é true. Se for false, novas cobranças funcionam mas o envio de USDT fica pendente até você cadastrar uma carteira.

Erros

Todas as respostas de erro retornam JSON com a estrutura abaixo. O HTTP status indica a categoria do erro.

json{
  "error": "validation_error",
  "message": "Dados inválidos",
  "details": { ... }
}
StatusCodeSignificado
400validation_errorBody inválido ou campo faltando
400amount_above_limitValor da cobrança ultrapassa o max_charge_brl da sua conta. Response inclui max_charge_brl com seu limite atual.
401unauthorizedAPI key ausente, inválida ou revogada
403profile_incompletePerfil do lojista não tem nome legal + WhatsApp cadastrados. Complete em Dashboard → Meu Perfil → Dados de Contato.
403account_blocked_medConta bloqueada por débito de MED (devolução PIX) acima do limite. Anexe documentação em Dashboard → Compliance.
403account_suspendedConta suspensa pelo operador. Contate o suporte.
404not_foundCobrança ou recurso não existe
429rate_limitLimite de requisições excedido (50.000/24h)
502psp_errorErro no PSP que processa o PIX
500internal_errorErro interno do servidor

Criar cobrança PIX

POST/v1/charges

Gera um QR code PIX que o pagador escaneia / copia-e-cola pra pagar. O valor já vem com taxa da plataforma calculada: 3% sobre o valor do PIX + R$ 0,55 fixo (a taxa fixa é aplicada em todas as cobranças, independente do valor).

Request body

CampoTipoDescrição
amount obrigatórionumberValor em reais. Mín R$ 1, máx limitado pelo seu max_charge_brl per-conta (default R$ 200, operator eleva caso a caso até R$ 100.000). Se ultrapassar, retorna 400 amount_above_limit com o seu limite atual.
external_idstringID do seu sistema (ex: ID do pedido). Usado pra consultar e idempotência
descriptionstringDescrição livre (até 255 chars)
expires_in_minutesnumberTempo de expiração. Default 30, máx 1440. Piso silencioso de 30min — valores menores são automaticamente elevados, porque o PSP pode confirmar pagamento depois do QR "expirar" e disparar pix.expired antes do pix.received quebra integrações que travam a cobrança no expired.
webhook_urlstring (URL)URL de callback específica desta cobrança. Recebe pix.received e pix.expired assinados. Alternativa ao cadastro no painel.
customer.namestringNome do pagador
customer.emailstringEmail do pagador
customer.documentstringCPF/CNPJ do pagador

Exemplo cURL

bashcurl -X POST https://pixusdt.org/v1/charges \
  -H "Authorization: Bearer pk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 49.90,
    "external_id": "pedido-12345",
    "description": "Plano Premium mensal",
    "webhook_url": "https://seu-sistema.com/webhook/pixusdt",
    "customer": {
      "name": "João Silva",
      "email": "joao@example.com",
      "document": "12345678901"
    }
  }'
💡 webhook_url inline: ao informar webhook_url no corpo da cobrança, o PixUSDT envia pix.received e pix.expired diretamente para essa URL — sem precisar cadastrar nada no painel. Útil quando seu sistema já sabe pra onde quer receber o callback antes de criar a cobrança (ex: integração com Gestor Inove). O payload e a assinatura HMAC-SHA256 são idênticos aos webhooks do painel.
📷 QR Code — três formatos:
  • pix.qr_code_base64 e pix.qr_code_image_url: strings prontas pra <img src=> (data URI data:image/png;base64,… ou URL externa). Use direto: <img src={pix.qr_code_image_url} />.
  • pix.qr_code_base64_raw: base64 puro (sem prefixo). Só use se você quiser montar o data URI manualmente: <img src={`data:image/png;base64,${pix.qr_code_base64_raw}`} />.
❌ NÃO faça: <img src={`data:image/png;base64,${pix.qr_code_base64}`} /> — isso duplica o prefixo e quebra a imagem (use qr_code_base64_raw nesse caso).

Response 201

json{
  "id": "1842",
  "external_id": "pedido-12345",
  "status": "pending",
  "amount": 49.90,
  "amount_cents": 4990,
  "fees": {
    "platform_fee_pct": 3.0,
    "percentage_fee_brl": 1.50,
    "fixed_fee_brl": 0.55,
    "total_fee_brl": 2.05,
    "net_brl": 47.85
  },
  "pix": {
    "qr_code": "00020126580014BR.GOV.BCB.PIX...",
    "qr_code_base64": "data:image/png;base64,iVBORw0...",
    "qr_code_image_url": "data:image/png;base64,iVBORw0...",
    "qr_code_base64_raw": "iVBORw0KGgoAAAANSUhEUgAAA...",
    "copy_paste": "00020126580014BR.GOV.BCB.PIX..."
  },
  "expires_at": "2026-05-05T18:30:00.000Z",
  "created_at": "2026-05-05T18:00:00",
  "psp_id": "a1b5323c-9e68-4afb-8927-94cdcef7a156"
}

Consultar cobrança

GET/v1/charges/{id}

Retorna o status atual de uma cobrança. O {id} pode ser tanto o id retornado pelo PixUSDT quanto o external_id que você passou no momento da criação.

bashcurl https://pixusdt.org/v1/charges/pedido-12345 \
  -H "Authorization: Bearer pk_live_xxx"

Status possíveis

StatusSignificado
pendingAguardando pagamento
paidPago e confirmado pelo PSP
expiredQR code expirou sem pagamento
failedErro no processamento
refundedEstornado

Listar cobranças

GET/v1/charges?status=paid&limit=50&offset=0
bashcurl "https://pixusdt.org/v1/charges?status=paid&limit=50" \
  -H "Authorization: Bearer pk_live_xxx"

Info da conta

GET/v1/account

Retorna informações do dono da API key. Útil pra validar a chave durante setup da integração.

Saldo

GET/v1/balance
json{
  "gross_received_brl": 12483.50,
  "pending_brl": 347.00,
  "sent_usdt_brl": 11250.00,
  "total_fees_brl": 374.50,
  "net_brl": 12109.00,
  "med": {
    "debt_brl": 0,
    "block_threshold_brl": 200,
    "account_blocked": false
  },
  "account_status": "active"
}

Campos

CampoDescrição
gross_received_brlTotal bruto recebido em PIX (todo histórico)
pending_brlCobranças pending aguardando pagamento
sent_usdt_brlEquivalente BRL do USDT já enviado pra sua carteira
total_fees_brlSoma das taxas da plataforma cobradas até hoje
net_brlSaldo líquido (gross − fees)
med.debt_brlDébito acumulado por MEDs (devoluções PIX) abertos
med.block_threshold_brlLimite a partir do qual a conta é bloqueada por MED
med.account_blockedtrue se conta bloqueada por MED — POST /v1/charges vai retornar 403 account_blocked_med
account_statusactive | suspended | blocked_med

Conversões USDT

Histórico de envios USDT confirmados pelo operador. Cada PIX pago gera uma transaction quando o USDT é enviado on-chain.

GET/v1/transactions?status=confirmed&network=BSC&limit=50
bashcurl "https://pixusdt.org/v1/transactions?status=confirmed" \
  -H "Authorization: Bearer pk_live_xxx"

Filtros disponíveis

Query paramTipoDescrição
statusstringpending | sent | confirmed
charge_idstringID interno PixUSDT da cobrança
external_idstringID que você passou na criação da cobrança
networkstringTRON | POLYGON | BSC
limitnumberDefault 50, máx 200
offsetnumberPaginação

Response

json{
  "data": [
    {
      "id": "42",
      "charge_id": "1842",
      "external_id": "pedido-12345",
      "amount_brl": 49.90,
      "usdt_amount": 9.18,
      "rate_brl": 5.22,
      "network": "BSC",
      "to_address": "0x95c4d336087276c291bade26b556562be4d52c7b",
      "tx_hash": "0x482cc3...0137",
      "explorer_url": "https://bscscan.com/tx/0x482cc3...0137",
      "network_fee_usd": 0.30,
      "status": "confirmed",
      "sent_at": "2026-05-06T18:23:10Z",
      "confirmed_at": "2026-05-06T18:23:42Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

Pra consultar uma conversão específica:

GET/v1/transactions/{id}

Eventos de Webhook

O PixUSDT envia POST para a URL configurada no dashboard sempre que um evento da sua conta acontece. Cadastre suas URLs em Dashboard → API Keys & Webhooks → Webhooks Configurados.

EventoQuando dispara
pix.receivedQuando uma cobrança PIX é confirmada como paga pelo PSP
pix.expiredQuando uma cobrança expira sem pagamento
usdt.sentQuando o USDT é enviado pra carteira do lojista (TX hash registrado)
usdt.confirmedQuando a transação USDT é confirmada na blockchain (em roadmap)

Exemplo de payload usdt.sent

json{
  "event": "usdt.sent",
  "timestamp": "2026-05-06T18:23:42.521Z",
  "data": {
    "charge": {
      "id": 1842,
      "external_id": "pedido-12345",
      "amount_brl": 49.90,
      "psp_id": "a1b5323c-9e68-4afb-8927-94cdcef7a156"
    },
    "transaction": {
      "id": 42,
      "usdt_amount": 9.18,
      "rate_brl": 5.22,
      "network": "BSC",
      "to_address": "0x95c4d336087276c291bade26b556562be4d52c7b",
      "tx_hash": "0x482cc3...0137",
      "network_fee": 0.30,
      "explorer_url": "https://bscscan.com/tx/0x482cc3...0137",
      "batch": false,
      "status": "sent"
    }
  }
}
Envio em lote: quando o operador envia USDT pra vários PIX de uma vez (1 TX hash cobre N cobranças), cada charge recebe um webhook usdt.sent separado, com transaction.batch: true e transaction.batch_size indicando quantas cobranças compartilham o mesmo tx_hash.

Payload do webhook

Todos os webhooks chegam como POST application/json com a estrutura:

json{
  "event": "pix.received",
  "timestamp": "2026-05-05T18:23:14.521Z",
  "data": {
    "charge": {
      "id": 1842,
      "amount_brl": 49.90,
      "status": "paid",
      "paid_at": "2026-05-05 18:23:10",
      "created_at": "2026-05-05 18:00:00",
      "psp_id": "a1b5323c-9e68-4afb-8927-94cdcef7a156",
      "payer_name": "João Silva",
      "payer_document": "123.456.789-01"
    },
    "client": null
  }
}

Headers enviados

httpContent-Type: application/json
X-Webhook-Signature: 4e8c5f3a2b1c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e
X-Webhook-Timestamp: 1714000000
X-Webhook-Id: 9f2d4e8a-1b3c-4d5e-8f9a-1b2c3d4e5f6a
X-Webhook-Source: pixusdt

Validação HMAC-SHA256 + Timestamp (anti-replay)

O PixUSDT assina o webhook em 2 etapas pra prevenir ataques de replay:

Sua validação deve fazer 2 checks:

  1. O timestamp está dentro de uma janela de tolerância (recomendado: ±5 minutos) — se for fora, rejeita (suspeita de replay)
  2. Recalcula HMAC-SHA256 sobre "{timestamp}.{body}" e compara em tempo constante (use hmac.compare_digest, crypto.timingSafeEqual, etc — nunca ===)

Node.js (Express)

javascriptimport crypto from 'node:crypto';
import express from 'express';

const SECRET = process.env.PIXUSDT_WEBHOOK_SECRET; // whsec_...
const TOLERANCE_SEC = 300; // 5 minutos

const app = express();
app.post('/webhook/pixusdt',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const ts  = req.header('X-Webhook-Timestamp');
    const sig = req.header('X-Webhook-Signature');
    if (!ts || !sig) return res.status(401).send('missing headers');

    // 1) Janela temporal
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(ts, 10)) > TOLERANCE_SEC) {
      return res.status(401).send('timestamp out of tolerance');
    }

    // 2) Recalcula HMAC sobre "timestamp.body"
    const expected = crypto.createHmac('sha256', SECRET)
      .update(`${ts}.${req.body.toString()}`)
      .digest('hex');

    // 3) Compara em tempo constante
    const ok = sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
    if (!ok) return res.status(401).send('invalid signature');

    const event = JSON.parse(req.body.toString());
    if (event.event === 'pix.received') {
      console.log('PIX confirmado:', event.data.charge.external_id);
    }
    res.status(200).json({ ok: true });
  }
);

PHP (Laravel)

php$secret    = env('PIXUSDT_WEBHOOK_SECRET');
$tolerance = 300; // 5 minutos

$body = $request->getContent();
$ts   = $request->header('X-Webhook-Timestamp');
$sig  = $request->header('X-Webhook-Signature');

if (!$ts || !$sig) abort(401, 'missing headers');

// 1) Janela temporal
if (abs(time() - intval($ts)) > $tolerance) {
    abort(401, 'timestamp out of tolerance');
}

// 2) Recalcula HMAC sobre "timestamp.body"
$expected = hash_hmac('sha256', $ts . '.' . $body, $secret);

// 3) Compara em tempo constante
if (!hash_equals($expected, $sig)) {
    abort(401, 'invalid signature');
}

$event = json_decode($body, true);
if ($event['event'] === 'pix.received') {
    Order::where('external_id', $event['data']['charge']['external_id'])
         ->update(['status' => 'paid']);
}

Python (FastAPI)

pythonimport hmac, hashlib, os, json, time
from fastapi import FastAPI, Request, HTTPException

SECRET = os.environ['PIXUSDT_WEBHOOK_SECRET'].encode()
TOLERANCE = 300  # 5 minutos
app = FastAPI()

@app.post('/webhook/pixusdt')
async def webhook(request: Request):
    body = await request.body()
    ts   = request.headers.get('x-webhook-timestamp', '')
    sig  = request.headers.get('x-webhook-signature', '')
    if not ts or not sig:
        raise HTTPException(401, 'missing headers')

    # 1) Janela temporal
    if abs(int(time.time()) - int(ts)) > TOLERANCE:
        raise HTTPException(401, 'timestamp out of tolerance')

    # 2) Recalcula HMAC sobre "timestamp.body"
    payload  = f"{ts}.{body.decode()}".encode()
    expected = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()

    # 3) Compara em tempo constante
    if not hmac.compare_digest(sig, expected):
        raise HTTPException(401, 'invalid signature')

    event = json.loads(body)
    if event['event'] == 'pix.received':
        ...  # processa pagamento
    return {'ok': True}
⚠ Mudança v1.1: a partir de 2026-05-06, a assinatura passou a incluir o timestamp pra prevenir ataques de replay. Se você implementou validação no formato antigo (HMAC só sobre o body), atualize seu código com o novo formato — o header X-Webhook-Timestamp agora é obrigatório.
Retentativas: se sua URL retornar status diferente de 2xx, o PixUSDT tenta novamente até 3 vezes com backoff exponencial (1s, 2s, 3s entre tentativas). Após 3 falhas, o webhook é descartado e contabilizado como falha no dashboard.

Rotação do Webhook Secret

Quando você regenera sua API key (botão 🔄 REGENERAR no dashboard), o webhook_secret também é trocado. A revogação é imediata — não há grace period no nosso lado:

Estratégia recomendada do lado cliente pra rotação sem perder webhooks:

  1. Configure 2 variáveis de ambiente: PIXUSDT_WEBHOOK_SECRET (atual) e PIXUSDT_WEBHOOK_SECRET_PREV (anterior)
  2. No handler do webhook, valide a assinatura contra ambos os secrets — aceita se qualquer um bater
  3. Quando rotacionar: pegue o secret novo no dashboard, copie o atual pra _PREV, coloque o novo em _SECRET, faça reload da app
  4. Após ~5 minutos (janela de webhooks em voo + retries), pode descartar o _PREV
javascript// Validação aceitando 2 secrets durante janela de rotação
const SECRETS = [
  process.env.PIXUSDT_WEBHOOK_SECRET,
  process.env.PIXUSDT_WEBHOOK_SECRET_PREV
].filter(Boolean);

function isValid(ts, body, sig) {
  return SECRETS.some(secret => {
    const expected = crypto.createHmac('sha256', secret)
      .update(`${ts}.${body}`)
      .digest('hex');
    return sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  });
}
⚠ Quando rotacionar? Em incidentes de segurança (suspeita de vazamento), no offboarding de funcionário com acesso, ou periodicamente (a cada 90-180 dias é boa prática). Não regenere por costume — é operação que exige cuidado e deploy coordenado no seu lado.

SDKs / Bibliotecas

Atualmente não fornecemos SDKs oficiais — a API é pequena o suficiente pra ser consumida com qualquer client HTTP (axios, requests, Guzzle, etc.). SDKs em Node.js, PHP e Python estão no roadmap.

Changelog

Dúvidas? Abra um ticket em /support ou mande email para suporte@pixusdt.org.

PixUSDT é uma solução de pagamento que converte PIX em USDT (stablecoin Tether). Não somos afiliados, parceiros ou licenciados pela Tether Limited. "USDT" refere-se ao ativo de liquidação utilizado pela plataforma.