API compatível com a Cryptomus

A blockwallets permite que você aceite USDT e USDC, além de BTC, LTC, DOGE, BCH e moedas nativas (creditadas em dólares americanos a uma cotação travada): o cliente paga, nós confirmamos na blockchain e avisamos você. A API, as assinaturas e os webhooks são os mesmos da Cryptomus: se você já integrou a Cryptomus, troque o domínio e pronto. Primeiro como funciona, depois a API.

Duas formas de receber

As duas aceitam qualquer valor: pedidos de preço fixo e recargas em que o cliente digita o valor. Depois de pago, você envia o pedido ou credita o saldo do cliente. A única diferença é se cada pagamento recebe um endereço novo.

Recomendado · a maioria das integraçõesCobranças

O cliente escolhe um produto ou digita um valor na sua página, você cria uma cobrança e ele envia o valor exibido na página de pagamento. Cada cobrança tem um endereço novo e uma contagem regressiva; quando o pagamento chega, avisamos você com o valor realmente recebido.

POST /v1/payment → webhook type: payment

OpcionalCarteiras estáticas

Sem valor definido antes: cada cliente recebe um endereço permanente por rede, para salvar na própria carteira e enviar a qualquer momento. Cada transferência dispara o seu próprio webhook.

POST /v1/wallet → webhook type: wallet

Em caso de dúvida, use cobranças. Sem código, os links de pagamento do painel são cobranças criadas para você. Ainda não é lojista? Solicite acesso na página inicial; depois de aprovado, entre no painel do lojista e comece por “Testar um pagamento” na página inicial do painel, pagando um valor pequeno (o dinheiro vai para o seu próprio saldo).

Como uma cobrança evolui

Um pagamento envolve quatro partes: o cliente, o seu site, a blockwallets e a blockchain. O diagrama corre de cima para baixo no tempo; a coluna da direita é o status da cobrança em cada etapa.

① Criar② Pagar③ Confirmar④ Notificar⑤ Concluir Clientenavegador + carteiraSeu sitepáginas + servidorblockwalletspagamento + scannerBlockchainTRON + 14 mais Status da cobrançastatus 1Clica em “Recarregar”2POST /v1/payment3uuid + urlcheck4Redireciona para url5Escolhe moeda + rede6Endereço + valor exclusivos7Envia para esse endereço8Varredura a cada minuto9Visto, aguardando confirmaçõesconfirm_check10Conferência com um segundo nópaid11POST url_callback12Responde 2xx13Verifique, envie / credite14De volta a url_success15Creditado no saldo

O diagrama é largo — role para os lados.

15 etapas, de cima para baixo em ordem cronológica. Toque em “Reproduzir” para assistir, ou toque em qualquer etapa para ver os detalhes.

Resumindo, cinco etapas:

  1. Você cria uma cobrança (um link de pagamento faz isso por você, ou o seu servidor chama /v1/payment), recebe a url da página de pagamento e redireciona o cliente para lá.
  2. Na página de pagamento, o cliente escolhe uma moeda e uma rede, recebe um endereço e um valor exclusivos e paga a partir de uma carteira ou de uma corretora.
  3. Vemos a transferência na blockchain, esperamos confirmações suficientes e então a conferimos com um segundo provedor de nós (para que um nó não consiga forjá-la).
  4. A cobrança vira paid e na mesma hora enviamos um webhook assinado por POST para o seu url_callback; alguns segundos depois, a página de pagamento devolve o cliente ao seu url_success.
  5. O dinheiro é creditado no seu saldo; saque para a sua própria carteira pelo painel do lojista.

Status da cobrança

statusSignificadoO que você faz
checkAguardando pagamento—
confirm_checkTransferência detectada, aguardando confirmações—
wrong_amount_waitingRecebido, mas insuficiente; o cliente pode complementar no mesmo endereço (quando is_payment_multiple é true; notificado uma vez)— (você recebe paid quando for complementado)
paidPagaEnvie / credite conforme payment_amount_usd
paid_overPaga, com pagamento a maisO mesmo (payment_amount_usd é o valor recebido)
wrong_amountExpirada com menos que o valor total (com is_payment_multiple false: a primeira transferência insuficiente)Negócios de recarga podem creditar o payment_amount_usd recebido; lojas pedem ao cliente que pague o restante
cancelExpirada sem nada recebido— (o dinheiro que chegar depois ainda é creditado e notificado)

Quando is_final é true, o status não muda mais (exceto por pagamentos tardios).

Painel do lojista → “Recebimentos › Links de pagamento” → informe uma descrição e um valor (fixe uma rede ou deixe o cliente escolher) → criar. Cada link tem uma URL e um QR code.

Integração por API

1. Credenciais e assinatura

Painel do lojista → “Desenvolvedores › Integração e chaves” → gere um merchant (UUID) e uma API key. Há um conjunto para Live e outro para Test: Live movimenta dinheiro real, Test roda em testnets. Cadastre também os domínios do seu site: url_callback, url_return e url_success precisam estar neles. A API key é exibida uma única vez: guarde-a bem e somente no seu servidor.

URL base https://blockwallets.io. Todo endpoint é POST + JSON, com estes cabeçalhos:

Em caso de sucesso retorna {"state": 0, "result": {…}}; em caso de falha, {"state": 1, "message": "…"} (veja Erros).

Os principais endpoints abaixo têm um painel “Testar”: informe o seu merchant e a API key de Test, e a página assina a requisição no seu navegador, chama o endpoint e mostra a requisição e a resposta completas (as credenciais ficam só na memória da página). Se uma assinatura não bater, compare-a passo a passo com o verificador de assinatura.

Node.js
import crypto from "node:crypto";

async function call(path, data) {
  const body = JSON.stringify(data);
  const sign = crypto.createHash("md5").update(Buffer.from(body).toString("base64") + process.env.BW_API_KEY).digest("hex");
  const res = await fetch("https://blockwallets.io" + path, {
    method: "POST",
    headers: { merchant: process.env.BW_MERCHANT, sign, "Content-Type": "application/json" },
    body,
  });
  return res.json();
}
PHP
function bw_call(string $path, array $data): array {
    $body = json_encode($data, JSON_UNESCAPED_UNICODE);
    $sign = md5(base64_encode($body) . getenv('BW_API_KEY'));
    $ch = curl_init('https://blockwallets.io' . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['merchant: ' . getenv('BW_MERCHANT'), 'sign: ' . $sign, 'Content-Type: application/json'],
    ]);
    return json_decode(curl_exec($ch), true);
}
Python
import base64, hashlib, json, os, requests

def bw_call(path: str, data: dict) -> dict:
    body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
    sign = hashlib.md5((base64.b64encode(body.encode()).decode() + os.environ["BW_API_KEY"]).encode()).hexdigest()
    headers = {"merchant": os.environ["BW_MERCHANT"], "sign": sign, "Content-Type": "application/json"}
    return requests.post("https://blockwallets.io" + path, data=body.encode(), headers=headers).json()

2. Criar uma cobrança POST /v1/payment

Quando o cliente faz um pedido, ou digita um valor na sua página de recarga, chame https://blockwallets.io/v1/payment e redirecione-o para o result.url retornado.

json
{
  "amount": "42.00",
  "currency": "USD",
  "order_id": "order_1042",
  "url_callback": "https://shop.example/webhooks/crypto",
  "url_return": "https://shop.example/cart",
  "url_success": "https://shop.example/thanks?order=1042"
}
ParâmetroObrigatórioDescrição
amountSimValor como string, com até duas casas decimais: "42.00". O seu preço ou um número digitado pelo cliente; não abaixo do mínimo da rede escolhida (veja redes)
currencySimUSD, USDT ou USDC, todas 1:1
order_idSimO número do seu pedido: letras, dígitos, -, _, ≤ 100 caracteres. Chamar de novo com o mesmo order_id retorna a cobrança existente (sem pedidos duplicados)
url_callbackNãoURL do webhook. Se omitida, nenhum webhook é enviado
url_returnNãoPara onde vai o “Voltar” antes de pagar
url_successNãoPara onde o cliente vai depois de pagar
networkNãoPré-selecionar uma rede: tron / bsc / eth / base / polygon / arbitrum / optimism / avalanche / xlayer / btc / ltc / doge / bch / sol / ton; se omitida, o cliente escolhe na página de pagamento
to_currencyNãoMoeda nessa rede: USDT / USDC; em btc / ltc / doge / bch é BTC / LTC / DOGE / BCH (opcional)
lifetimeNãoDuração da cobrança em segundos, de 300 a 43200, padrão 3600; qualquer valor abaixo de 600 conta como 600
accuracy_payment_percentNãoQuanto de pagamento a menos ainda conta como pago, de 0 a 5, padrão 0. 100 = aceitar o que chegar: paid assim que confirmar (paid_over se for mais), crédito por payment_amount_usd no webhook — bom para recargas. Se omitido, vale a “Regra de valor” em “Configurações › Regras de recebimento e taxas” no painel: “creditar o que chegar” significa 100
subtractNãoParcela da taxa da plataforma paga pelo cliente, de 0 a 100, somada a payer_amount (a taxa da plataforma é 1%, menor com mais volume; a 1%, com 100, uma cobrança de $100 custa ao cliente $101.00; veja os preços). Se omitido, vale a configuração “Taxas e pagamentos a menos” em “Configurações › Regras de recebimento e taxas” no painel (padrão 0)
is_payment_multipleNãoSe um pagamento a menos pode ser complementado, padrão true: status wrong_amount_waiting; pagar o restante no mesmo endereço a torna paid (se restarem menos de 30 minutos no primeiro pagamento a menos, o prazo é movido para 30 minutos depois). false: a primeira transferência insuficiente a torna wrong_amount. Se omitido, vale a configuração do painel
additional_dataNãoQualquer string (≤ 255), devolvida como está nas consultas e nos webhooks
json
{
  "state": 0,
  "result": {
    "uuid": "6f1c2a7e-…",
    "order_id": "order_1042",
    "amount": "42.00",
    "currency": "USD",
    "network": null,
    "address": null,
    "payer_amount": null,
    "payment_amount": null,
    "url": "https://blockwallets.io/pay/inv_01J8…",
    "expired_at": 1760965200,
    "status": "check",
    "is_final": false
  }
}

network, address e payer_amount são preenchidos assim que o cliente escolhe uma rede.

3. Consultar cobranças

Os webhooks são o canal principal; as consultas são o plano B (por exemplo, se o seu servidor estava fora do ar e perdeu um webhook).

4. Carteiras estáticas POST /v1/wallet

Abre um endereço permanente para um cliente em uma rede. O mesmo order_id (normalmente o ID do seu usuário) + rede sempre dá o mesmo endereço; todas as redes EVM (BNB Chain, Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, X Layer) compartilham um endereço, enquanto TRON, BTC, LTC, DOGE, BCH, Solana e TON têm cada uma o seu. Tudo que o cliente enviar é creditado, com um webhook por transferência; as transferências em BTC / LTC / DOGE / BCH / TRX / BNB / ETH / POL / AVAX / OKB / SOL / TON são convertidas em dólares americanos pela cotação do momento em que cada uma foi vista (payment_amount_usd).

json
{
  "order_id": "user_42",
  "currency": "USDT",
  "network": "tron",
  "url_callback": "https://shop.example/webhooks/crypto"
}
json
{
  "state": 0,
  "result": {
    "wallet_uuid": "0b9d…",
    "uuid": "5e2f…",
    "order_id": "user_42",
    "currency": "USDT",
    "network": "tron",
    "address": "TXy4…8kQ2",
    "url": "https://blockwallets.io/deposit/dac_01J8…"
  }
}

result.url é uma página de depósito hospedada (endereço + QR code + depósitos recentes) que você pode entregar ao cliente. Transferências abaixo do mínimo da rede (geralmente $1, BTC $5) não são creditadas automaticamente; fale com a gente para creditá-las. O bloqueio de endereços (/v1/wallet/block-address) ainda não é suportado.

Webhooks

Quando o status de uma cobrança muda (paid, paid_over, wrong_amount_waiting, wrong_amount, cancel) e a cada transferência para uma carteira estática, enviamos um POST com JSON para url_callback. Uma resposta 2xx conta como entregue; caso contrário, tentamos de novo após 1 minuto, 5 minutos, 30 minutos, 2 horas, 12 horas, 24 horas e 24 horas. Você também pode reenviar pelo painel ou com /v1/payment/resend.

json
{
  "type": "payment",
  "uuid": "6f1c2a7e-…",
  "order_id": "order_1042",
  "amount": "42.00",
  "payment_amount": "42",
  "payment_amount_usd": "42.00",
  "merchant_amount": "41.58",
  "commission": "0.42",
  "is_final": true,
  "status": "paid",
  "from": "TQ8m…",
  "wallet_address_uuid": null,
  "network": "tron",
  "currency": "USD",
  "payer_currency": "USDT",
  "payer_amount": "42",
  "payer_amount_exchange_rate": "1",
  "additional_data": null,
  "txid": "9c1e…",
  "sign": "5f0c9b…"
}
typeQuandoCrédito
paymentO status de uma cobrança mudou — veja statusCredite payment_amount_usd em paid / paid_over; deduplique por uuid
walletUma transferência chegou a uma carteira estática (status é sempre paid; order_id é aquele com que você abriu a carteira)Credite payment_amount_usd; cada transferência tem o seu próprio uuid — deduplique por ele

Verificar a assinatura (obrigatório)

A assinatura é o campo sign do corpo do webhook: remova sign, recodifique o resto como o json_encode do PHP faz (compacto, / escapada como \/, caracteres não ASCII sem escape) e md5(base64(encoded) + API key) deve ser igual a sign. Use a API key do mesmo modo. O mesmo webhook pode chegar mais de uma vez — deduplique por uuid + status.

Se a verificação falhar, cole o corpo bruto do webhook no verificador de assinatura: ele mostra o resultado de cada etapa e marca onde a sua difere (tudo roda no seu navegador; a API key nunca é enviada). Todo webhook de “Desenvolvedores › Log de webhooks” no painel abre no verificador com um clique.

Node.js
import crypto from "node:crypto";

function verify(rawBody, apiKey) {
  const { sign, ...data } = JSON.parse(rawBody);
  const json = JSON.stringify(data).replaceAll("/", "\\/");
  const expected = crypto.createHash("md5").update(Buffer.from(json).toString("base64") + apiKey).digest("hex");
  return typeof sign === "string" && sign.length === expected.length && crypto.timingSafeEqual(Buffer.from(sign), Buffer.from(expected));
}
PHP
$data = json_decode(file_get_contents('php://input'), true);
$sign = $data['sign'] ?? '';
unset($data['sign']);
$expected = md5(base64_encode(json_encode($data, JSON_UNESCAPED_UNICODE)) . $apiKey);
if (!hash_equals($expected, $sign)) {
    http_response_code(401); exit;
}
Python
import base64, hashlib, hmac, json

def verify(raw_body: bytes, api_key: str) -> bool:
    data = json.loads(raw_body)
    sign = data.pop("sign", "")
    encoded = json.dumps(data, separators=(",", ":"), ensure_ascii=False).replace("/", "\\/")
    expected = hashlib.md5((base64.b64encode(encoded.encode()).decode() + api_key).encode()).hexdigest()
    return hmac.compare_digest(expected, sign)

Modo de teste

As cobranças criadas com o merchant e a API key de Test rodam em testnets, sem dinheiro real. A página de pagamento mostra um botão “Simular pagamento” (total / metade / a mais), para você ver as mudanças de status e os webhooks sem precisar de moedas de testnet.

Migrando da Cryptomus

  1. Painel “Desenvolvedores › Integração e chaves” → gere um merchant e uma API key.
  2. Troque https://api.cryptomus.com no seu código por https://blockwallets.io e use o novo merchant e a nova API key.
  3. Cadastre os seus domínios em “Domínios do site” (url_callback / url_return / url_success só aceitam esses).

Suportados: /v1/payment, /v1/payment/info, /v1/payment/list, /v1/payment/services, /v1/payment/resend, /v1/wallet, /v1/test-webhook/payment, /v1/test-webhook/wallet. Diferenças: as cobranças são precificadas apenas em USD / USDT / USDC (todas 1:1, sem spread de câmbio); commission é a nossa taxa da plataforma (o valor recebido × a taxa do lojista quando a cobrança foi criada — 1% padrão, chegando a 0.5% conforme o volume; nada é cobrado duas vezes sobre a parte que o cliente cobre) e merchant_amount = recebido − commission; quando o cliente paga em BTC / LTC / DOGE / BCH / TRX / BNB / ETH / POL / AVAX / OKB / SOL / TON, cotamos a mediana de várias corretoras, travada por 30 minutos — payer_amount é o número de moedas a enviar, payer_amount_exchange_rate é o valor em USD de uma moeda, e o pagamento é convertido em payment_amount_usd pela cotação travada (as moedas ficam conosco, nada é trocado); redes tron / bsc / eth / base / polygon / arbitrum / optimism / avalanche / xlayer / btc / ltc / doge / bch / sol / ton; duração mínima da cobrança de 10 minutos; sem is_refresh e sem bloqueio de carteiras estáticas; por padrão os webhooks vêm de IPs variáveis; se você usa lista de IPs permitidos, ative “Webhooks com IP fixo” no painel, em “Desenvolvedores › Integração e chaves”, e os webhooks virão apenas de 18.163.145.41 — uma lista de permitidos é uma camada extra, continue verificando sign.

Vindo da NOWPayments ou da Coinbase Commerce? Temos APIs compatíveis com as duas — troque a URL base e as chaves; veja o guia de migração.

Redes e valores

MoedaRedeParâmetro networkMínimoChegada
USDTTRONtron$1~1 min
USDTBNB Chainbsc$1~1 min
USDCBNB Chainbsc$1~1 min
USDTEthereumeth$5~1 min
USDCEthereumeth$5~1 min
USDCBasebase$1~1 min
USDTPolygonpolygon$1~1 min
USDCPolygonpolygon$1~1 min
USDTArbitrumarbitrum$1~1 min
USDCArbitrumarbitrum$1~1 min
USDTOptimismoptimism$1~1 min
USDCOptimismoptimism$1~1 min
USDTAvalancheavalanche$1~1 min
USDCAvalancheavalanche$1~1 min
USDTX Layerxlayer$1~1 min
USDCX Layerxlayer$1~1 min
USD1BNB Chainbsc$1~1 min
FDUSDBNB Chainbsc$1~1 min
DAIEthereumeth$5~1 min
DAIArbitrumarbitrum$1~1 min
DAIBasebase$1~1 min
BTCBitcoinbtc$5~10 min
LTCLitecoinltc$1~3 min
DOGEDogecoindoge$1~1 min
BCHBitcoin Cashbch$1~10 min
TRXTRONtron$1~1 min
BNBBNB Chainbsc$1~1 min
ETHEthereumeth$5~1 min
ETHBasebase$1~1 min
ETHArbitrumarbitrum$1~1 min
ETHOptimismoptimism$1~1 min
POLPolygonpolygon$1~1 min
AVAXAvalancheavalanche$1~1 min
OKBX Layerxlayer$1~1 min
USDTSolanasol$1~1 min
USDCSolanasol$1~1 min
SOLSolanasol$1~1 min
USDTTONton$1~1 min
TONTONton$1~1 min

As taxas do ERC-20 são altas, então o mínimo é $5; as do Bitcoin também não são baixas, mínimo $5; todo o resto é $1. Os clientes precisam enviar pela rede exibida na página de pagamento — dinheiro enviado por outra rede não é creditado automaticamente. BTC / LTC / DOGE / BCH aparecem como “detectado” assim que entram na mempool e só contam depois de confirmações suficientes; transações substituídas (RBF) ou descartadas não contam. As moedas nativas (TRX, BNB, ETH, POL, AVAX, OKB, SOL, TON) são cotadas a uma cotação travada e creditadas em dólares americanos como BTC / LTC / DOGE / BCH; informe a rede e a moeda: cobranças network: "bsc", to_currency: "BNB", carteiras estáticas network: "bsc", currency: "BNB". Uma rede sozinha equivale, por padrão, a USDT nessa rede (USDC na Base). Transferências feitas por contratos (multisig, pagamentos em lote) também são recebidas; em algumas redes o hash da transação não está disponível e txid no webhook é null.

Erros

json
{ "state": 1, "message": "amount must be a number like 10.28" }
HTTPCausas comuns
422Parâmetros ausentes ou malformados; valor abaixo do mínimo da rede; URL de callback / redirecionamento fora dos seus domínios cadastrados; moeda / rede não suportada em /v1/payment
401Merchant errado, sign errado (Invalid Sign.) ou API key trocada
404A cobrança não existe ou não é sua (Payment not found); em /v1/wallet, combinação de moeda / rede não suportada (The service was not found)
503A rede está temporariamente indisponível — escolha outra ou não pré-selecione nenhuma

Quando a assinatura está errada (Invalid Sign.), a resposta traz um objeto debug: body_base64 é o base64 do corpo da requisição que recebemos (o seu próprio conteúdo, nunca a API key) e body_bytes o seu tamanho. Compare com o base64 que você assinou: se forem diferentes, o corpo que você enviou não é a string que você assinou (o seu framework serializou de novo, um proxy mudou a codificação, uma quebra de linha no final); se forem iguais, a API key está errada. Cole no verificador de assinatura para ver a primeira diferença.

json
{
  "state": 1,
  "message": "Invalid Sign.",
  "debug": {
    "body_base64": "eyJhbW91bnQiOiI0Mi4wMCIsImN1cnJlbmN5IjoiVVNEIiwi…",
    "body_bytes": 158,
    "sign_formula": "md5(body_base64 + API key)"
  }
}

Perguntas frequentes

Os clientes recarregam o valor que quiserem — qual modo?

Cobranças. Deixe o cliente digitar um valor na sua página de recarga, crie uma cobrança para ele e credite payment_amount_usd quando chegar. Use carteiras estáticas só se os clientes precisarem salvar um endereço e enviar para ele repetidamente.

E se um cliente pagar a menos?

Antes de expirar, o cliente pode enviar a diferença pela página de pagamento (ela mostra quanto ainda falta; status wrong_amount_waiting, e se restarem menos de 30 minutos no primeiro pagamento a menos, o prazo é movido para 30 minutos depois). Se ainda faltar na expiração, a cobrança vira wrong_amount e payment_amount_usd no webhook é o valor recebido; negócios de recarga podem simplesmente creditar esse valor.

E se um cliente pagar com atraso, ou duas vezes?

O dinheiro que chega depois da expiração ainda é creditado e notificado; o dinheiro que chega depois de a cobrança já estar totalmente paga também é registrado e fica visível no painel.

E se um cliente enviar pela rede errada?

Por exemplo, a cobrança é em TRON e ele envia pela BNB Chain: não é creditado automaticamente — entre em contato e resolvemos manualmente.

Quanto custa?

1% de cada pagamento recebido, reduzido automaticamente pelo volume do mês anterior (0.9% a partir de $50,000, 0.8% a partir de $100,000, 0.7% a partir de $250,000, 0.6% a partir de $500,000 e 0.5% a partir de $1,000,000), sem mensalidade nem taxa de adesão. O cliente paga a taxa de rede ao enviar; para repassar essa taxa ao cliente, defina subtract ao criar uma cobrança ou como padrão no painel. Os saques não têm taxa extra. Veja os preços.

Quando o dinheiro chega até mim?

É creditado no seu saldo assim que o pagamento confirma. Solicite um saque para a sua própria carteira em “Fundos › Saldo e saques”; depois que aprovamos, fazemos o pagamento e a transação na blockchain aparece no seu histórico de saques.

Você também pode ativar o saque automático: quando o seu saldo disponível atingir um valor que você definir, ou uma vez por dia às 00:00 UTC, todo o saldo disponível é sacado. Os saques automáticos são revisados do mesmo jeito, e toda alteração de configuração envia um aviso para o seu e-mail.