PT

Documentação da API do comerciante

URL base: https://snapex.pro/api/v1/merchant

Chave de API e primeira requisição

  1. Obtenha uma chave no painel

    Painel do parceiro → Desenvolvedores → “Obter chave de API”. A chave tem o formato smk_abcdefghij_plus48chars e é exibida uma única vez. Se você perdê-la, gere uma nova: a chave anterior deixa de funcionar na hora.

  2. Adicione um header

    Authorization: Bearer your_key.

  3. Verifique o acesso

    Consulte o saldo. Uma resposta JSON com data significa que a chave foi aceita.

requisição
curl -sS https://snapex.pro/api/v1/merchant/balances \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"
resposta
{
  "data": [
    {
      "asset_id": "usdt",               // sempre usdt — não existe outro saldo
      "available_atomic": "15000000",   // 15 USDT em unidades mínimas (6 casas decimais)
      "available": "15",                // o mesmo valor em forma decimal
      "held_atomic": "0",               // reservado para saques não concluídos
      "held": "0"
    }
  ]
}

Se a requisição falhou

Resposta Causa
401 unauthorized A chave está faltando, contém um espaço ou está cortada. Depois de Bearer deve vir o identificador smk_… completo.
401 key_inactive A chave foi revogada: uma nova chave foi gerada, mas a requisição ainda usa a anterior.

Cinco regras

A maioria dos erros de integração vem do descumprimento de uma destas regras.

1. Envie os valores como strings

Envie amount — uma string decimal: "0.001", "15" — ou amount_atomic — um inteiro em unidades mínimas: "100000" para 0.001 BTC. Use exatamente um dos dois campos. As respostas sempre trazem as duas formas: amount vem ao lado de amount_atomic.

exemplo
"amount": "0.001"        // válido
"amount_atomic": "100000" // também válido
// os dois ao mesmo tempo — 422

2. Pegue asset_id apenas de /assets

Códigos permitidos: usdt, btc, xmr. Valores arbitrários e ids de exploradores de blocos são rejeitados com 422.

exemplo
"asset_id": "btc"   // correto
"asset_id": "BTC"   // erro
"asset_id": "bitcoin" // erro

3. Idempotency-Key em toda requisição que cria algo

Um id único da tentativa. Se a conexão cair e o resultado for desconhecido, repita a requisição com a mesma chave: o resultado original é retornado e uma segunda operação não é criada. Obrigatório para POST /invoices, POST /payments, POST /payouts/preview e POST /payouts.

exemplo
Idempotency-Key: order-42-payment   // primeira tentativa → 201, criado
Idempotency-Key: order-42-payment   // nova tentativa → 200, o mesmo recurso
Idempotency-Key: order-42-payment   // outro valor → 409 conflict

4. Campos vazios são omitidos

Valores null e strings vazias não são incluídos. Um pagamento que ainda não foi recebido não tem actual_amount, credited_amount nem fee. Verifique se a chave existe, não o valor dela.

exemplo
// pagamento ainda não recebido:
{ "payment_id": "pay_…", "status": "awaiting_deposit" }
// credited_amount simplesmente não aparece

5. Não feche o pedido com expired

expired não significa que a transferência nunca vai chegar. O endereço continua sendo monitorado por cerca de mais 6 horas, e uma transferência atrasada é creditada. Status finais com crédito: confirmed, confirmed_underpaid, confirmed_overpaid.

Cenários

Cada cenário cobre o ciclo completo: ordem das requisições, formato da resposta e significado dos campos. Os comentários nos exemplos JSON servem de explicação e não aparecem nas respostas reais.

Receber pagamentos

Cliente paga em BTC um pedido de 60 USD

Pedido order-42. Você precisa de cerca de 60 USDT; com o BTC a cerca de 60.000, o cliente deve enviar 0.001 BTC.

1
Obtenha a lista de ativos disponíveis

A resposta traz o código do ativo e decimals — o número de dígitos depois do ponto decimal.

requisição
curl -sS https://snapex.pro/api/v1/merchant/assets \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

GET /assets
Nenhum parâmetro é obrigatório.
Authorization
O único header obrigatório na API.
resposta
{
  "data": [
    {
      "asset_id": "btc",       // ← use nas próximas requisições
      "symbol": "BTC",         // para mostrar ao usuário
      "network": "bitcoin",    // usado nos saques
      "decimals": 8,           // 1 BTC = 100000000 unidades mínimas
      "price_usd": "60000"     // indicativo, não serve para cotação
    },
    {
      "asset_id": "usdt",
      "symbol": "USDT",
      "network": "TRON",
      "decimals": 6,
      "price_usd": "1.00"
    }
  ]
}
2
Trave a cotação por 10 minutos

Trava a cotação enquanto o cliente está na página de pagamento. Nesta etapa não é criado nenhum pagamento nem endereço: só é calculada uma cotação. A resposta traz quantos USDT serão creditados por 0.001 BTC.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payments/credit-preview \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"asset_id":"btc","amount":"0.001"}'

Pontos principais

asset_id: "btc"
Código da etapa anterior.
amount: "0.001"
Valor em BTC esperado do cliente. Enviado como string, não como número.
Idempotency-Key
Opcional: nenhuma operação é criada. Você pode enviá-la para reutilizar a mesma cotação.
resposta
{
  "data": {
    "credit_lock_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                                       // ← guarde para a próxima etapa
    "asset_id": "btc",
    "expected_amount": "0.001",        // valor esperado do cliente
    "expected_amount_atomic": "100000",
    "credit_usdt": "59.4",             // ← valor que será creditado
    "credit_usdt_atomic": "59400000",
    "expires_at": "2026-08-11T17:20:00+00:00"
                                       // ← depois deste horário o credit-preview perde a validade
  }
}

Mostre ao cliente o expected_amount — quanto BTC ele deve enviar.

3
Crie o pagamento e obtenha o endereço

Esta etapa cria o pagamento e retorna o endereço de depósito. Não envie o valor nesta requisição — ele já foi definido no credit-preview, na etapa 2.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payments \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: order-42-payment" \
  -d '{
    "credit_lock_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "external_id": "order-42"
  }'

Pontos principais

credit_lock_id
Id da resposta do credit-preview. Se a cotação travada tiver expirado, a API retorna 422 — solicite um novo preview.
external_id: "order-42"
O número do seu pedido. Serve para encontrar o pagamento depois e para evitar dois pagamentos para o mesmo pedido.
Idempotency-Key
Obrigatória. Use um valor único e estável, por exemplo o número do pedido com um sufixo.
resposta
{
  "data": {
    "payment_id": "pay_01HZX...",      // ← id para consultar o status
    "external_id": "order-42",         // número do pedido enviado na requisição
    "asset_id": "btc",
    "expected_amount": "0.001",
    "credit_usdt": "59.4",
    "status": "awaiting_deposit",      // aguardando a transferência
    "channel": "api",
    "deposit_address": "bc1qexampledepositaddress000000000",
                                       // ← endereço a mostrar ao cliente
    "expires_at": "2026-08-11T18:00:00+00:00",
    "created_at": "2026-08-11T17:00:00+00:00"
  }
}

Se a rede exigir memo, a resposta inclui deposit_memo. Uma transferência sem o memo não será creditada — mostre os dois campos.

4
Aguarde o pagamento

Consulte o pagamento a cada 10–30 segundos. O status segue awaiting_deposit → detected → processing → confirmed.

requisição
curl -sS https://snapex.pro/api/v1/merchant/payments/pay_01HZX... \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

GET /payments/{payment_id}
Use o payment_id da resposta anterior.
Quando
O intervalo recomendado é de 10–30 segundos. O limite é de 120 requisições por minuto por chave; não consulte com mais frequência.
resposta
{
  "data": {
    "payment_id": "pay_01HZX...",
    "status": "detected",              // transferência vista na rede,
                                       // ainda sem confirmações suficientes
    "asset_id": "btc",
    "expected_amount": "0.001",
    "actual_amount": "0.001",          // ← valor que realmente chegou
    "deposit_address": "bc1qexampledepositaddress000000000",
    "created_at": "2026-08-11T17:00:00+00:00"
  }
}
5
Pagamento confirmado

confirmed significa que os fundos foram creditados no saldo. O pedido pode ser fechado.

requisição
curl -sS https://snapex.pro/api/v1/merchant/payments/pay_01HZX... \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

status: confirmed
Status final; não vai mudar.
credited_amount
USDT efetivamente creditados. Em um pagamento exato, coincide com credit_usdt.
resposta
{
  "data": {
    "payment_id": "pay_01HZX...",
    "external_id": "order-42",
    "status": "confirmed",             // ← crédito concluído
    "asset_id": "btc",
    "expected_amount": "0.001",
    "actual_amount": "0.001",          // chegou o valor esperado
    "credit_usdt": "59.4",
    "credited_amount": "59.4",         // ← valor creditado no saldo
    "fee": "0.6",                      // taxa, já incluída em credited_amount
    "deposit_address": "bc1qexampledepositaddress000000000",
    "created_at": "2026-08-11T17:00:00+00:00"
  }
}

Concilie pelo credited_amount, não pelo expected_amount: em um pagamento parcial, os valores são diferentes.

Observação

  • Não crie o pagamento com antecedência: a cotação fica travada por 10 minutos, e o endereço é gerado para um valor específico.
  • Não feche o pedido em detected: os fundos ainda não foram creditados.

Cliente paga em USDT

O fluxo é o mesmo do cenário com BTC; só muda o asset_id. Veja abaixo um exemplo mais curto.

1
Trave o valor

Não há conversão, então credit_usdt é praticamente o valor menos a taxa.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payments/credit-preview \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -d '{"asset_id":"usdt","amount":"100"}'

Pontos principais

asset_id: "usdt"
A única diferença em relação ao cenário com BTC.
amount: "100"
O cliente envia 100 USDT na rede TRON.
resposta
{
  "data": {
    "credit_lock_id": "b2c3d4e5-...",
    "asset_id": "usdt",
    "expected_amount": "100",
    "credit_usdt": "99",         // ← taxa de 1 USDT
    "expires_at": "2026-08-11T17:20:00+00:00"
  }
}
2
Crie o pagamento

A mesma etapa de antes. O endereço é da rede TRON (começa com T).

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payments \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-43-payment" \
  -d '{"credit_lock_id":"b2c3d4e5-...","external_id":"order-43"}'

Pontos principais

Idempotency-Key
Use uma nova Idempotency-Key para cada novo pedido. Não reutilize a chave do order-42.
resposta
{
  "data": {
    "payment_id": "pay_01HZY...",
    "external_id": "order-43",
    "asset_id": "usdt",
    "expected_amount": "100",
    "credit_usdt": "99",
    "status": "awaiting_deposit",
    "deposit_address": "TXYZexampletronaddress000000000",
                                 // ← endereço TRON, TRC20
    "expires_at": "2026-08-11T18:00:00+00:00"
  }
}

Informe ao cliente que a rede é TRC20. USDT enviados por outra rede não serão creditados neste endereço.

Cliente enviou menos do que o solicitado

Eram esperados 0.001 BTC e chegaram 0.0009 — por exemplo, a carteira do cliente descontou a taxa de rede do valor.

1
Verifique o pagamento

Se chegou menos que o expected_amount, mas o valor ainda é suficiente para ser creditado, o status é confirmed_underpaid e o credited_amount fica abaixo da cotação. Se o valor for pequeno demais, nada é creditado.

requisição
curl -sS https://snapex.pro/api/v1/merchant/payments/pay_01HZX... \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

Significado
Compare credited_amount, não credit_usdt. O primeiro é o que foi creditado; o segundo é a cotação para um pagamento exato.
resposta
{
  "data": {
    "payment_id": "pay_01HZX...",
    "external_id": "order-42",
    "status": "confirmed_underpaid",   // ← pagamento a menor, mas os fundos foram creditados
    "asset_id": "btc",
    "expected_amount": "0.001",        // valor esperado
    "actual_amount": "0.0009",         // valor que realmente chegou
    "credit_usdt": "59.4",             // cotação para um pagamento exato
    "credited_amount": "53.4",         // ← valor realmente creditado
    "fee": "0.6",
    "created_at": "2026-08-11T17:00:00+00:00"
  }
}

Os próximos passos ficam com você: pedir que o cliente complete o pagamento, entregar o pedido em parte ou cancelá-lo. A API retorna o valor efetivamente creditado.

Observação

  • Não decida “pago ou não” comparando actual_amount com expected_amount. Compare credited_amount com os USDT de que você precisa.

Cliente não pagou a tempo

O expires_at passou e nenhuma transferência chegou. O pagamento passa para expired; o endereço continua sendo monitorado.

1
Status expired

O prazo de pagamento terminou. O endereço continua sendo monitorado por cerca de 6 horas.

requisição
curl -sS https://snapex.pro/api/v1/merchant/payments/pay_01HZX... \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

status: expired
O prazo de pagamento expirou.
credited_amount
Nenhum valor chegou ainda.
resposta
{
  "data": {
    "payment_id": "pay_01HZX...",
    "status": "expired",               // prazo encerrado
    "asset_id": "btc",
    "expected_amount": "0.001",
    "deposit_address": "bc1qexampledepositaddress000000000",
    "expires_at": "2026-08-11T18:00:00+00:00"
    // actual_amount e credited_amount ausentes: nenhuma transferência chegou
  }
}
2
Cliente pagou com atraso

Se a transferência chegar dentro do prazo extra de monitoramento, o pagamento ainda passa para confirmed. Não exclua o pedido em expired: marque-o como atrasado e continue consultando.

requisição
curl -sS https://snapex.pro/api/v1/merchant/payments/pay_01HZX... \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

expired → confirmed
Caso normal: a transferência chegou depois do expires_at.
resposta
{
  "data": {
    "payment_id": "pay_01HZX...",
    "status": "confirmed",             // ← creditado depois de expires_at
    "actual_amount": "0.001",
    "credited_amount": "59.4",
    "created_at": "2026-08-11T17:00:00+00:00"
  }
}

Observação

  • Não gere um novo endereço logo depois de expired: o cliente pode já estar enviando para o anterior. Continuamos aguardando a transferência por mais 6 horas.

Faturas

Uma fatura em USD com página de pagamento hospedada. O cliente escolhe a rede; você recebe o crédito em USDT.

Uma fatura de 15 USD paga em USDT

Pedido order-15. O comerciante cria uma fatura em USD e envia ao cliente o link da página de pagamento.

1
Crie a fatura

A fatura fixa o valor em USD e quem paga a taxa, e retorna pay_url. Ainda não há endereço.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/invoices \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: order-15-invoice" \
  -d '{
    "amount_usd": "15.00",
    "fee_on": "merchant",
    "external_id": "order-15"
  }'

Pontos principais

amount_usd
Valor do pedido como string. Este endpoint aceita só USD, não amount_atomic.
fee_on
merchant: você paga a taxa e o cliente vê 15.00. customer: o cliente paga a taxa.
Idempotency-Key
Obrigatória. Uma nova tentativa com a mesma chave retorna a mesma fatura.
partner_ref
Opcional. O Partner ID do integrador que configurou sua integração (programa de indicações da SnapEX Pay). Vincula sua conta a esse parceiro uma única vez; omita se você mesmo faz a integração.
resposta
{
  "data": {
    "invoice_id": "INV-8K2M1Q0P",
    "amount_usd": "15.00",
    "fee_on": "merchant",
    "charge_usd": "15.00",
    "credit_usd": "13.85",
    "status": "open",
    "pay_url": "https://pay.snapex.pro/0123456789abcdef0123456789abcdef",
    "methods": [
      { "asset_id": "usdt.trc20", "symbol": "USDT", "network": "TRC20" },
      { "asset_id": "usdt.erc20", "symbol": "USDT", "network": "ERC20" },
      { "asset_id": "usdt.bep20", "symbol": "USDT", "network": "BEP20" }
    ]
  }
}

A chave precisa de invoices.write ou payments.write. Modo de teste: environment=test.

2
O cliente escolhe a rede

Esta etapa gera um endereço exclusivo. O método pode ser trocado até chegar uma transferência.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/invoices/INV-8K2M1Q0P/methods \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-15-trc20" \
  -d '{"asset_id":"usdt.trc20"}'

Pontos principais

asset_id: "usdt.trc20"
Um código de methods: usdt.trc20, usdt.erc20 ou usdt.bep20. As outras moedas vêm da mesma lista.
payment.address
Mostre só este endereço e este valor. Memo e Tag não são usados.
resposta
{
  "data": {
    "invoice_id": "INV-8K2M1Q0P",
    "status": "awaiting",
    "selected_asset_id": "usdt.trc20",
    "payment": {
      "asset_id": "usdt.trc20",
      "amount": "15 USDT",
      "address": "TExampleUniqueAddressForThisAttempt",
      "status": "awaiting"
    }
  }
}

Você pode pular esta chamada: o cliente escolhe a rede no pay_url.

3
A fatura está paga

A primeira transferência concluída fecha a fatura. Uma transferência posterior para outro endereço é creditada à parte.

requisição
curl -sS https://snapex.pro/api/v1/merchant/invoices/INV-8K2M1Q0P \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

status: paid
Status final. Você pode fechar o pedido.
invoice.paid
O webhook invoice.paid chega à URL configurada no painel. Verificação da assinatura HMAC: veja a seção “Webhooks”.
resposta
{
  "data": {
    "invoice_id": "INV-8K2M1Q0P",
    "status": "paid",
    "credit_usd": "13.85",
    "paid_at": "2026-09-25T12:00:10+00:00"
  }
}

Um pagamento a menor não fecha a fatura: o status fica underpaid e o mesmo endereço aguarda o restante (USDT: 72 horas). Se o restante não chegar a tempo, a fatura termina como partially_paid.

Observação

  • Não cite rotas internas na interface do cliente. A página de pagamento mostra só o endereço, o valor e a rede.
  • Não há API de reembolso. Uma segunda transferência concluída é um crédito separado, não um estorno da primeira.

Saques

Transferências de saída do seu saldo em USDT.

Envie ao cliente 15 USDT em BTC

amount é quanto sai do seu saldo, não quanto o destinatário recebe. O valor que o destinatário recebe é retornado pelo preview.

1
Verifique o saldo disponível

Use available, não available mais held. O held está reservado para saques não concluídos e não pode ser usado.

requisição
curl -sS https://snapex.pro/api/v1/merchant/balances \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

available
Saldo disponível para débito.
held
Reservado para saques em andamento.
resposta
{
  "data": [
    {
      "asset_id": "usdt",
      "available": "74.4",       // ← disponível para uso
      "available_atomic": "74400000",
      "held": "0",               // nada reservado
      "held_atomic": "0"
    }
  ]
}
2
Faça a cotação do saque

O preview retorna quanto BTC o destinatário recebe por 15 USDT e a taxa. Nada é debitado ainda: só a cotação fica travada.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payouts/preview \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: wd-77-preview" \
  -d '{
    "destination_asset_id": "btc",
    "destination_network": "bitcoin",
    "destination_address": "bc1qexampledestinationaddress000000",
    "amount": "15"
  }'

Pontos principais

amount: "15"
USDT debitados do seu saldo. Não é o valor que o destinatário recebe.
destination_asset_id
Ativo do destinatário. Código de GET /assets.
destination_network
Rede do destinatário. Para BTC — bitcoin. Pegue o valor de network em GET /assets.
destination_address
Endereço do destinatário. Confira antes de enviar: a transferência não pode ser cancelada.
Idempotency-Key
Obrigatória. Use uma chave separada para o preview.
resposta
{
  "data": {
    "preview_id": "c3d4e5f6-7890-abcd-ef12-34567890abcd",
                                     // ← id para a próxima etapa
    "from_asset_id": "usdt",
    "from_amount": "15",             // valor debitado
    "to_asset_id": "btc",
    "to_amount": "0.000221",         // ← valor que o destinatário recebe
    "fee_amount": "0.000019",        // taxa em BTC, já incluída
    "expires_at": "2026-08-11T17:20:00+00:00"
                                     // ← execute antes deste horário
  }
}

Mostre to_amount ao usuário como o valor que ele vai receber. Se o valor não for aceitável, não execute o preview: ele expira em expires_at.

3
Execute o saque

Esta etapa debita e envia. Repita o endereço e a rede na requisição — isso impede que os dados sejam substituídos entre uma etapa e outra.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payouts \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: wd-77-execute" \
  -d '{
    "preview_id": "c3d4e5f6-7890-abcd-ef12-34567890abcd",
    "destination_network": "bitcoin",
    "destination_address": "bc1qexampledestinationaddress000000",
    "external_id": "wd-77"
  }'

Pontos principais

Idempotency-Key: wd-77-execute
Uma chave nova, diferente da chave do preview. Reutilizar a chave do preview retorna 409.
destination_network / address
Deve coincidir exatamente com o preview; caso contrário, 422.
external_id
O id da sua operação, para consultar depois.
resposta
{
  "data": {
    "payout_id": "payout_01HZX...",  // ← id para acompanhamento
    "external_id": "wd-77",
    "asset_id": "usdt",
    "debit_amount": "15",            // debitado do saldo
    "destination_asset_id": "btc",
    "destination_network": "bitcoin",
    "destination_address": "bc1qexampledestinationaddress000000",
    "destination_amount": "0.000221",// valor que o destinatário recebe
    "fee": "0.000019",
    "status": "reserved",            // fundos reservados, envio iniciado
    "channel": "api",
    "created_at": "2026-08-11T17:10:00+00:00"
  }
}
4
Aguarde a conclusão

Sequência de status: reserved → submitting → processing → completed. Consulte a cada 15–30 segundos.

requisição
curl -sS https://snapex.pro/api/v1/merchant/payouts/payout_01HZX... \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

status: completed
Fundos entregues. Status final.
status: failed
A transferência falhou; a reserva normalmente volta para o saldo.
status: manual_review
O resultado é incerto. Aguarde a análise; não crie outro saque.
resposta
{
  "data": {
    "payout_id": "payout_01HZX...",
    "external_id": "wd-77",
    "debit_amount": "15",
    "destination_amount": "0.000221",
    "status": "completed",           // ← entregue
    "created_at": "2026-08-11T17:10:00+00:00"
  }
}

Observação

  • amount é o valor debitado, não o valor que o destinatário recebe.
  • Os saques têm um limite próprio: 5 requisições por minuto e 30 por hora. Não chame o preview em loop.
  • Se o preview expirou (expires_at), POST /payouts retorna 422. Solicite um novo preview.

Envie USDT para uma carteira externa na rede TRON

A diferença é que destination_asset_id também é usdt. Neste caso, a taxa é mínima.

1
Faça a cotação do saque

O ativo debitado e o de destino são os mesmos, então to_amount é praticamente amount menos a taxa de rede.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payouts/preview \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wd-78-preview" \
  -d '{
    "destination_asset_id": "usdt",
    "destination_network": "TRON",
    "destination_address": "TXYZexampletronaddress000000000",
    "amount": "50"
  }'

Pontos principais

destination_network: "TRON"
Pegue o valor de network em GET /assets; maiúsculas e minúsculas fazem diferença.
amount: "50"
Serão debitados 50 USDT.
resposta
{
  "data": {
    "preview_id": "d4e5f6a7-...",
    "from_asset_id": "usdt",
    "from_amount": "50",           // valor a debitar
    "to_asset_id": "usdt",
    "to_amount": "48.9",           // ← valor recebido (descontada a taxa de rede)
    "fee_amount": "1.1",
    "expires_at": "2026-08-11T17:25:00+00:00"
  }
}
2
Execute o saque

A mesma sequência do cenário anterior.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payouts \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wd-78-execute" \
  -d '{
    "preview_id": "d4e5f6a7-...",
    "destination_network": "TRON",
    "destination_address": "TXYZexampletronaddress000000000",
    "external_id": "wd-78"
  }'
resposta
{
  "data": {
    "payout_id": "payout_01HZZ...",
    "debit_amount": "50",
    "destination_asset_id": "usdt",
    "destination_amount": "48.9",
    "status": "reserved",
    "created_at": "2026-08-11T17:26:00+00:00"
  }
}

Observação

  • Confirme que o endereço do destinatário é da rede TRON. Uma transferência para outra rede não é executada: a requisição é rejeitada ou os fundos não vão chegar.

Saldo insuficiente para um saque

O saldo é de 10 USDT e foi solicitado um saque de 15.

1
O preview recusa a requisição

O saldo é verificado na cotação, antes de qualquer débito. O erro pode ser mostrado ao usuário na hora.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payouts/preview \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wd-79-preview" \
  -d '{
    "destination_asset_id": "btc",
    "destination_network": "bitcoin",
    "destination_address": "bc1q…",
    "amount": "15"
  }'

Pontos principais

HTTP 422
Recusa por regra de negócio. O envelope é o mesmo dos erros de validação.
details.amount
O erro fica vinculado a um campo e pode ser mostrado no formulário.
resposta
{
  "error": {
    "code": "validation_error",
    "message": "The request is invalid.",
    "details": {
      "amount": ["Saldo insuficiente."]
                              // ← o motivo da recusa está vinculado a amount
    }
  }
}

Nada é reservado. Repetir com a mesma Idempotency-Key é seguro; antes, recarregue o saldo.

Observação

  • Considere o held: se parte dos fundos estiver reservada para outro saque, o available será menor que o esperado.

Operações

Histórico de pagamentos

O ledger é um registro de operações: créditos, débitos e reservas ficam guardados como lançamentos separados.

1
Obtenha o histórico por páginas

Os lançamentos vêm do mais recente para o mais antigo. Até 100 registros por requisição; a próxima página usa o cursor.

requisição
curl -sS "https://snapex.pro/api/v1/merchant/transactions?limit=50" \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

limit=50
Registros por resposta: de 1 a 100, padrão 25.
status=payment
Filtro opcional por tipo de lançamento.
resposta
{
  "data": [
    {
      "id": "124",
      "direction": "credit",          // crédito
      "type": "payment",              // origem: depósito
      "amount": "59.4",
      "asset_id": "usdt",
      "channel": "api",
      "occurred_at": "2026-08-11T17:05:00+00:00"
    },
    {
      "id": "125",
      "direction": "reserve",         // reserva para um saque
      "type": "payout",
      "amount": "15",
      "asset_id": "usdt",
      "occurred_at": "2026-08-11T17:10:00+00:00"
    }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6MTI1fQ",  // ← valor para o parâmetro cursor
    "per_page": 50
  }
}
2
Próxima página

Quando next_cursor é null, não há mais registros.

requisição
curl -sS "https://snapex.pro/api/v1/merchant/transactions?limit=50&cursor=eyJpZCI6MTI1fQ" \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Accept: application/json"

Pontos principais

cursor
Valor de meta.next_cursor da resposta anterior. Um valor arbitrário não é aceito.
resposta
{
  "data": [ /* mais registros */ ],
  "meta": {
    "next_cursor": null,              // ← última página
    "per_page": 50
  }
}

Observação

  • Os valores reserve e release de direction são uma retenção e uma liberação, não uma transferência de saída. Não os conte como gasto.

Sem resposta: repita a mesma requisição

1
Repita a mesma requisição

Use a mesma Idempotency-Key e os mesmos parâmetros. O servidor trata como a mesma operação.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payouts \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wd-77-execute" \
  -d '{
    "preview_id": "c3d4e5f6-...",
    "destination_network": "bitcoin",
    "destination_address": "bc1qexampledestinationaddress000000",
    "external_id": "wd-77"
  }'

Pontos principais

Idempotency-Key
Uma chave nova cria um novo saque.
HTTP 200 / 201
A operação já existia; é retornado o objeto original.
resposta
{
  "data": {
    "payout_id": "payout_01HZX...",   // ← o mesmo id da primeira resposta
    "external_id": "wd-77",
    "debit_amount": "15",
    "status": "processing",           // o status pode ter mudado durante a espera
    "created_at": "2026-08-11T17:10:00+00:00"
  }
}

HTTP 201: a operação foi criada nesta requisição. HTTP 200: ela já existia. Os dois códigos indicam sucesso.

2
Se os parâmetros mudaram

Repetir a mesma chave com outro valor ou outro endereço é recusado. Isso evita divergência de parâmetros.

requisição
curl -sS -X POST https://snapex.pro/api/v1/merchant/payouts \
  -H "Authorization: Bearer smk_abcdefghij_yourkey" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wd-77-execute" \
  -d '{
    "preview_id": "another-preview",
    "destination_network": "bitcoin",
    "destination_address": "bc1qanotheraddress"
  }'

Pontos principais

HTTP 409
Conflito: a chave já está vinculada a outra operação.
O que fazer
Se for um novo saque, use uma nova Idempotency-Key.
resposta
{
  "error": {
    "code": "conflict",
    "message": "Idempotency-Key was used with different parameters."
  }
}

Observação

  • Gere a chave uma única vez e guarde-a junto com o pedido. Gerar uma chave nova antes de cada envio desativa a proteção contra duplicidade.
  • Em caso de HTTP 429, repita com a mesma chave após alguns segundos.

Webhooks

Quando uma fatura, um depósito, um saque, uma conversão ou o saldo mudam, a SnapEX envia uma requisição POST para a sua URL. O webhook é um sinal, não a fonte da verdade: verifique a assinatura HMAC e, para decisões importantes, consulte o objeto de novo pela API.

Configuração

  1. Configure a URL no painel

    Painel do parceiro → Desenvolvedores → bloco “Webhook”: informe a URL e clique em “Salvar”. Uma URL por conta; ela recebe todos os tipos de evento da tabela abaixo.

  2. Guarde a chave secreta de assinatura

    Ao salvar pela primeira vez, o painel mostra a chave secreta whsec_… uma única vez. Guarde-a apenas no seu servidor. Se você mudar a URL depois, a chave secreta continua a mesma.

  3. Requisitos da URL

    Uma URL https:// absoluta com host público: sem login:password na URL, sem localhost, e o host não pode resolver para um IP privado ou reservado. A URL é verificada ao salvar e de novo antes de cada tentativa de entrega.

O que chega

Header Significado
Webhook-Id Id único do evento (UUID), igual ao id no corpo. Não muda nas novas tentativas nem nos reenvios manuais — use-o para deduplicar.
Webhook-Timestamp Horário Unix em segundos em que esta tentativa foi enviada. Cada nova tentativa traz um valor novo e uma assinatura nova.
Webhook-Signature v1= seguido do HMAC-SHA256 em hex minúsculo da string “{Webhook-Timestamp}.{raw body}”. A chave é a chave secreta inteira, incluindo o prefixo whsec_.
Content-Type Sempre application/json. O corpo é JSON em UTF-8.
requisição
POST /hooks/snapex HTTP/1.1
Content-Type: application/json
Webhook-Id: 6f1c2a8e-3b4d-4f5a-9c7e-1a2b3c4d5e6f
Webhook-Timestamp: 1790337610
Webhook-Signature: v1=2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
corpo
{
  "id": "6f1c2a8e-3b4d-4f5a-9c7e-1a2b3c4d5e6f",   // = Webhook-Id
  "type": "invoice.paid",                          // tipo de evento, veja a tabela abaixo
  "created_at": "2026-09-25T12:00:10+00:00",       // quando o evento aconteceu
  "data": {                                        // o objeto no momento do evento
    "invoice_id": "INV-8K2M1Q0P",
    "environment": "live",
    "amount_usd": "15.00",
    "charge_usd": "15.00",
    "credit_usd": "13.85",
    "status": "paid",
    "external_id": "order-15",
    "paid_at": "2026-09-25T12:00:10+00:00"
  }
}

data depende do tipo: invoice.* — a fatura no mesmo formato de GET /invoices/{id} (attempts vem vazio, a tentativa atual está em payment); payment.* — o depósito (payment_id, external_id, status, valores); payout.* — o saque (payout_id, external_id, status, valores); conversion.* — a conversão (conversion_id, status, valores); balance.changed — asset_id, entry_type, available_atomic e held_atomic após a alteração.

Tipos de evento

Evento Significado
invoice.created Fatura criada; nenhuma forma de pagamento escolhida ainda (open).
invoice.awaiting Uma forma de pagamento foi escolhida e o endereço foi gerado (awaiting).
invoice.underpaid Parte do valor chegou; o mesmo endereço aguarda o restante (underpaid).
invoice.paid O valor total foi pago e creditado (paid, final). É o único sinal para entregar o pedido.
invoice.partially_paid O cliente pagou só uma parte, o prazo para completar o pagamento terminou e a parte recebida foi creditada para você (partially_paid, final).
invoice.expired O prazo da fatura terminou sem pagamento (expired, final).
invoice.extra_payment Outra transferência chegou depois que a fatura foi paga; ela é creditada separadamente.
payment.created Um depósito foi criado.
payment.detected A transferência foi vista na rede, mas ainda não foi creditada.
payment.credited O depósito foi creditado (um dos status confirmed*).
payment.expired O prazo do depósito expirou.
payout.created Um saque foi criado e os fundos foram reservados.
payout.processing O saque está sendo enviado.
payout.completed Os fundos foram entregues ao destinatário.
payout.failed O saque falhou.
conversion.completed A conversão foi concluída.
conversion.failed A conversão falhou total ou parcialmente.
balance.changed Um lançamento de saldo foi registrado: crédito, reserva ou débito.

Entregue o pedido somente com invoice.paid — ele só é enviado quando o valor total foi pago. invoice.underpaid e invoice.partially_paid significam que o valor está incompleto.

Faturas criadas com environment=test são simuladas: passam pelos mesmos status e enviam os mesmos webhooks (data.environment = "test"), mas nunca alteram o saldo real.

Entrega e novas tentativas

  • Sucesso é qualquer resposta 2xx. Qualquer outro código, um timeout ou um erro de rede conta como falha. O corpo da resposta não é interpretado.
  • Timeouts: 5 s para conectar, 15 s para a requisição inteira.
  • Redirecionamentos não são seguidos: 301/302 conta como falha. Configure a URL final.
  • Até 5 tentativas por evento: a primeira e depois novas tentativas cerca de 10 s, 1 min, 5 min e 15 min após cada falha. Depois da quinta falha, a entrega automática para.
  • Na mesma página, o bloco “Entregas do webhook” mostra evento, código HTTP ou erro, horário e número da tentativa. O botão “Reenviar” envia o evento de novo com o mesmo Webhook-Id.
  • A entrega é at-least-once (pelo menos uma vez): um evento pode chegar várias vezes, e os eventos podem chegar fora de ordem (um evento reenviado pode chegar depois de um mais recente).

Verificação da assinatura

  1. Leia o corpo bruto da requisição como bytes, antes de fazer o parse do JSON. Um JSON serializado de novo não vai bater com a assinatura.
  2. Calcule o HMAC-SHA256 de “{Webhook-Timestamp}.{raw body}” usando sua chave secreta whsec_… como chave e codifique em hex.
  3. Compare “v1=” + hex com o header Webhook-Signature usando uma função de tempo constante: hash_equals, crypto.timingSafeEqual, hmac.compare_digest.
  4. Rejeite um Webhook-Timestamp que difira do seu relógio em mais de 5 minutos (recomendado). As novas tentativas trazem um timestamp novo, então isso não as quebra.
PHP
<?php
$secret = getenv('SNAPEX_WEBHOOK_SECRET'); // whsec_… do painel
$body = file_get_contents('php://input');  // corpo bruto, antes do parse do JSON
$timestamp = $_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '';

$expected = 'v1='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);
if (! ctype_digit($timestamp)
    || abs(time() - (int) $timestamp) > 300
    || ! hash_equals($expected, $signature)) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// ignore se este Webhook-Id já foi processado; faça o trabalho pesado em uma fila
http_response_code(200);

Checklist do handler

  • Verifique a assinatura sobre o corpo bruto com uma comparação de tempo constante; responda 400 se ela não bater.
  • Rejeite requisições cujo Webhook-Timestamp tenha mais de 5 minutos.
  • Guarde os Webhook-Id já processados e ignore as repetições. Não dependa da ordem: compare o status do evento com o que você já tem.
  • Responda 2xx rápido, bem antes dos 15 segundos, e faça o trabalho pesado de forma assíncrona.
  • Depois de um webhook, você pode consultar o objeto de novo pela API para confirmar: GET /invoices/{id}, GET /payments/{id}, GET /payouts/{id}.

Status

O selo “final” indica que o status não vai mais mudar. A única exceção são os depósitos expirados: uma transferência atrasada ainda pode chegar.

Depósitos

Status Significado O que fazer
awaiting_deposit Endereço gerado, aguardando a transferência Mostre ao cliente o endereço e o valor
detected Transferência vista na rede, ainda não creditada Aguarde. Não feche o pedido
processing Convertendo para USDT Aguarde
confirmed O valor esperado chegou, fundos creditados Feche o pedido final
confirmed_underpaid Chegou menos que o esperado, dentro da margem permitida Confira o credited_amount e decida se é suficiente final
confirmed_overpaid Chegou mais que o esperado, creditado conforme o credit-preview Feche o pedido. Fale com o suporte sobre o excedente final
confirmed_mismatch Mantido por compatibilidade Não é usado em pagamentos novos final
manual_review Revisão manual necessária Aguarde a revisão; não crie um segundo pagamento
expired O prazo de pagamento expirou, o endereço continua monitorado por mais ~6 h Não exclua o pedido; continue consultando
failed Falha do lado do provedor Crie um novo pagamento final
cancelled Pagamento cancelado Crie um novo pagamento final
refunded Reembolsado pelo provedor Concilie o saldo; fale com o suporte se não bater final

Faturas

Status Significado O que fazer
open Fatura criada, forma de pagamento não escolhida Passe ao cliente o pay_url ou escolha o asset_id você mesmo
awaiting Endereço gerado, aguardando a transferência Mostre o endereço e o valor; consulte o status
underpaid Parte do valor chegou; mesmo endereço Peça ao cliente que envie o restante para este endereço
paid A primeira transferência completa foi creditada Feche o pedido final
partially_paid O cliente pagou só uma parte, o prazo para completar o pagamento terminou e a parte recebida foi creditada para você Não entregue como pedido pago; acerte o restante com o cliente final
expired O prazo da fatura terminou Crie uma nova fatura final

Saques

Status Significado O que fazer
reserved Fundos reservados para o saque Aguarde
submitting Enviado ao provedor Aguarde
processing O provedor está enviando on-chain Aguarde
completed Fundos entregues ao destinatário Pronto final
manual_review Resultado incerto Não crie outro saque; aguarde a revisão
failed A transferência falhou; a reserva normalmente é liberada Confira o saldo; se precisar, tente de novo com uma nova Idempotency-Key final

Erros

Uma resposta de sucesso sempre vem em data. Um erro sempre vem em error. Não existem outros envelopes.

formato do erro
{
  "error": {
    "code": "validation_error",     // código legível por máquina, veja a tabela abaixo
    "message": "The request is invalid.",
    "details": {                    // nem sempre está presente
      "destination_address": ["The destination address field is required."]
                                    // a chave é o campo que não passou na validação
    }
  }
}
HTTP code Quando O que fazer
401 unauthorized Chave ausente ou inválida Confira o header Authorization: Bearer smk_… por inteiro, sem espaços nem quebras de linha
401 key_inactive Chave revogada A chave anterior foi revogada. Use a chave atual do painel
401 key_expired Chave expirada Gere uma nova chave
404 not_found Id não encontrado ou pertence a outro comerciante Use o payment_id ou payout_id da sua própria resposta
409 conflict A Idempotency-Key está vinculada a outra operação, ou o external_id já foi usado Para uma nova operação, use uma nova chave. Para repetir, use a mesma chave e os mesmos parâmetros
422 validation_error Campos inválidos, credit-preview expirado, saldo insuficiente ou Idempotency-Key ausente Veja em details o campo e o motivo
429 rate_limited Limite de requisições excedido Tente de novo mais tarde com a mesma Idempotency-Key
500 internal_error Erro interno do servidor Tente de novo mais tarde com a mesma Idempotency-Key. Se o erro continuar, fale com o suporte e informe o X-Request-Id