# API REST nativa da blockwallets

> A API REST nativa da blockwallets: chaves Bearer, cobranças em centavos de dólar com refs idempotentes, contas de depósito, webhooks assinados com HMAC-SHA256, eventos e reenvio.
>
> HTML: https://blockwallets.io/docs/api?lang=pt · English: https://blockwallets.io/docs/api.md?lang=en

## API REST nativa

A API nativa é a interface REST própria da blockwallets: cobranças em centavos de dólar, um `ref` idempotente por pedido, contas de depósito estáticas que cobrem todas as redes e webhooks assinados com HMAC-SHA256. Ela cria as mesmas cobranças na mesma página de pagamento hospedada que a [API compatível com a Cryptomus](https://blockwallets.io/docs/cryptomus?lang=pt), com as mesmas taxas e o mesmo saldo.

Gere as suas chaves você mesmo no painel, em “Desenvolvedores › Integração e chaves”: uma chave Live e uma Test com as respectivas chaves de assinatura de webhook. Cada uma é mostrada uma única vez; a lista só mostra o prefixo. Se a sua conta tem a verificação em duas etapas ativa, gerar ou rotacionar uma chave Live pede um código. Ainda não tem conta de lojista? [Solicite acesso](https://blockwallets.io/?lang=pt#join). Já usa a Cryptomus? Veja a [API compatível com a Cryptomus](https://blockwallets.io/docs/cryptomus?lang=pt) ([como escolher](https://blockwallets.io/docs?lang=pt#which)).

### Chaves e requisições

URL base `https://blockwallets.io`. Requisições e respostas são em JSON. Toda requisição leva:

- `Authorization: Bearer bw_live_…` ou `Bearer bw_test_…`: o prefixo define o modo, então uma chave Test só enxerga cobranças Test e roda em testnets

- `Content-Type: application/json` nas requisições `POST`

- Guarde as chaves apenas no seu servidor, nunca numa página web ou app; nós armazenamos só o hash delas

Os erros retornam um status HTTP e `{"error": {"code": "…", "message": "…"}}`: 400 `invalid_request` (campo ausente ou malformado), 401 `unauthorized` (chave ausente, desconhecida ou desativada), 404 `not_found`, 409 `conflict` (a requisição conflita com o estado atual da cobrança), 422 `currency_unsupported`.

### Endpoints

| Endpoint | O que faz |
| --- | --- |
| `GET /v1/networks` | Redes que você pode aceitar agora no modo desta chave: mínimo, confirmações, tempo típico de chegada e taxa estimada; as moedas cotadas incluem a cotação atual |
| `POST /v1/invoices` | Cria uma cobrança (201), ou retorna a existente para o mesmo `ref` (200) |
| `GET /v1/invoices?ref=…` | Consulta uma cobrança pelo seu `ref` |
| `GET /v1/invoices/:id` | Consulta uma cobrança pelo id |
| `POST /v1/invoices/:id/network` | Escolha você mesmo a rede de uma cobrança (página de pagamento white-label): retorna o `address` e o `pay_amount` a exibir |
| `POST /v1/invoices/:id/close` | Fecha uma cobrança pendente que não recebeu nenhuma transferência |
| `POST /v1/deposit-accounts` | Abre (201) ou obtém (200) a conta de depósito de um cliente: um endereço permanente em cada rede |
| `GET /v1/deposit-accounts?customer_ref=…` | Consulta uma conta de depósito pelo `customer_ref` |
| `GET /v1/deposit-accounts/:id` | Consulta uma conta de depósito pelo id |
| `GET /v1/deposits` | Lista os depósitos de uma conta (`account_id` ou `customer_ref`, `limit` 1–200, padrão 50) |
| `GET /v1/deposits/:id` | Consulta um depósito |
| `GET /v1/events` | Entregas de webhook de uma cobrança ou depósito (`invoice_id` ou `deposit_id`) |
| `POST /v1/events/:id/redeliver` | Envia um webhook de novo (202) |

Adicione `?probe=1` à consulta de uma cobrança ou conta de depósito para verificar a blockchain na hora (no máximo uma vez a cada 10 segundos); caso contrário você recebe o último estado do nosso scanner, que verifica a cada minuto.

### Criar uma cobrança POST /v1/invoices

Chame `POST /v1/invoices` quando o cliente finalizar a compra e redirecione-o para a `pay_url` da resposta.

```curl
curl -X POST https://blockwallets.io/v1/invoices \
  -H "Authorization: Bearer $BW_KEY" -H "Content-Type: application/json" \
  -d '{"ref":"order_1042","amount":4200,"title":"Order #1042",
       "return_url":"https://shop.example/cart","success_url":"https://shop.example/thanks?order=1042",
       "meta":{"user_id":42}}'
```

| Campo | Obrigatório | Descrição |
| --- | --- | --- |
| `ref` | Sim | O número do seu pedido: 1–64 caracteres entre letras, dígitos e `_ . : -`. Único por modo; enviar o mesmo `ref` com os mesmos campos retorna a mesma cobrança, com campos diferentes retorna 409 |
| `amount` | Sim | Valor em centavos de dólar, inteiro: `4200` = $42.00. Não pode ficar abaixo do mínimo da rede depois de escolhida |
| `title` | Sim | O que o cliente vê na página de pagamento, até 64 caracteres |
| `return_url` | Sim | Para onde vai o “Voltar” na página de pagamento; deve estar em um dos domínios do seu site |
| `success_url` | Não | Para onde o cliente vai depois de pagar; o padrão é `return_url` |
| `network` | Não | Pré-seleciona um id de rede da [tabela de redes](https://blockwallets.io/docs/api?lang=pt#networks); omita para o cliente escolher na página de pagamento |
| `customer_ref` | Não | O id do seu cliente, até 64 caracteres, devolvido como está |
| `lifetime` | Não | Duração em segundos, 600 – 43200, padrão 3600 |
| `amount_rule` | Não | `"any"` = credita o que chegar (recargas): `paid` assim que confirmar. Omita para usar a “Regra de valor” em “Configurações › Regras de recebimento e taxas” no painel |
| `accuracy_percent` | Não | Quanto de pagamento a menos ainda conta como pago, 0 – 5 (por cento), padrão 0 |
| `subtract_percent` | Não | Parte da taxa da plataforma paga pelo cliente, 0 – 100 (por cento), somada ao `pay_amount`; omita para usar o padrão do painel |
| `payment_multiple` | Não | Se um pagamento insuficiente pode ser complementado no mesmo endereço, padrão do painel (ligado, a menos que você tenha mudado); `false`: a primeira transferência insuficiente termina como `wrong_amount` |
| `meta` | Não | Qualquer objeto JSON de até 2 KB, devolvido como está nas consultas e nos webhooks |
| `currency` | Não | Somente `USD` (o padrão); qualquer outro retorna 422 |

Toda URL deve estar em um dos domínios do seu site (painel → “Desenvolvedores › Integração e chaves”). A resposta é o objeto cobrança abaixo; `network`, `address` e `pay_amount` são preenchidos assim que uma rede é escolhida.

### O objeto cobrança

Os ids começam com `inv_`. `amount` está em centavos de dólar; `pay_amount` e `paid_amount` são valores da moeda, em strings decimais, na rede escolhida.

```json
{
  "id": "inv_01J8…",
  "ref": "order_1042",
  "mode": "live",
  "status": "pending",
  "amount": 4200,
  "currency": "USD",
  "title": "Order #1042",
  "meta": { "user_id": 42 },
  "network": null,
  "address": null,
  "pay_amount": null,
  "paid_amount": null,
  "payments": [],
  "pay_url": "https://blockwallets.io/pay/inv_01J8…",
  "return_url": "https://shop.example/cart",
  "success_url": "https://shop.example/thanks?order=1042",
  "created_at": "2026-11-03T08:00:00.000Z",
  "expires_at": "2026-11-03T09:00:00.000Z",
  "paid_at": null,
  "closed_at": null
}
```

| status | Significado | O que você faz |
| --- | --- | --- |
| `pending` | Aguardando pagamento | — |
| `confirming` | Uma transferência foi detectada e está confirmando, ou chegou incompleta e ainda pode ser complementada | — |
| `paid` | Paga | Envie / credite o pedido |
| `paid_over` | Paga, com pagamento a mais | O mesmo; o excedente fica no seu saldo |
| `wrong_amount` | Terminou com menos que o valor total (na expiração, ou na primeira transferência insuficiente quando `payment_multiple` é false) | Negócios de recarga podem creditar o que chegou (`paid_amount`; moedas com cotação também trazem `paid_cents`); lojas pedem ao cliente que pague o restante |
| `closed` | Expirou sem receber nada, ou você a fechou | — (o dinheiro que chegar depois ainda é creditado e notificado) |

Outros campos: `pay_url` (a página de pagamento hospedada), `payments` (cada transferência com `txid`, `amount`, `confirmations` e `confirmed`), `expires_at`, `paid_at`, `closed_at`; BTC, LTC, DOGE, BCH e moedas nativas também trazem `rate` (dólares por moeda, travado por 30 minutos), `rate_locked_until` e `paid_cents` (o que já chegou, em centavos de dólar).

### Redes

Passe o id como `network`. `GET /v1/networks` retorna as que estão abertas agora no seu modo; uma rede pode ser pausada, então prefira consultar a deixá-la fixa no código.

| network | Moeda | Rede | Mínimo |
| --- | --- | --- | --- |
| `USDT_TRC20` | USDT | TRON | $1 |
| `USDT_BEP20` | USDT | BNB Chain | $1 |
| `USDC_BEP20` | USDC | BNB Chain | $1 |
| `USDT_ERC20` | USDT | Ethereum | $5 |
| `USDC_ERC20` | USDC | Ethereum | $5 |
| `USDC_BASE` | USDC | Base | $1 |
| `USDT_POLYGON` | USDT | Polygon | $1 |
| `USDC_POLYGON` | USDC | Polygon | $1 |
| `USDT_ARBITRUM` | USDT | Arbitrum | $1 |
| `USDC_ARBITRUM` | USDC | Arbitrum | $1 |
| `USDT_OPTIMISM` | USDT | Optimism | $1 |
| `USDC_OPTIMISM` | USDC | Optimism | $1 |
| `USDT_AVALANCHE` | USDT | Avalanche | $1 |
| `USDC_AVALANCHE` | USDC | Avalanche | $1 |
| `USDT_XLAYER` | USDT | X Layer | $1 |
| `USDC_XLAYER` | USDC | X Layer | $1 |
| `USD1_BEP20` | USD1 | BNB Chain | $1 |
| `FDUSD_BEP20` | FDUSD | BNB Chain | $1 |
| `DAI_ERC20` | DAI | Ethereum | $5 |
| `DAI_ARBITRUM` | DAI | Arbitrum | $1 |
| `DAI_BASE` | DAI | Base | $1 |
| `BTC` | BTC | Bitcoin | $5 |
| `LTC` | LTC | Litecoin | $1 |
| `DOGE` | DOGE | Dogecoin | $1 |
| `BCH` | BCH | Bitcoin Cash | $1 |
| `TRX` | TRX | TRON | $1 |
| `BNB` | BNB | BNB Chain | $1 |
| `ETH` | ETH | Ethereum | $5 |
| `ETH_BASE` | ETH | Base | $1 |
| `ETH_ARBITRUM` | ETH | Arbitrum | $1 |
| `ETH_OPTIMISM` | ETH | Optimism | $1 |
| `POL` | POL | Polygon | $1 |
| `AVAX` | AVAX | Avalanche | $1 |
| `OKB` | OKB | X Layer | $1 |
| `USDT_SOL` | USDT | Solana | $1 |
| `USDC_SOL` | USDC | Solana | $1 |
| `SOL` | SOL | Solana | $1 |
| `USDT_TON` | USDT | TON | $1 |
| `TON` | TON | TON | $1 |

### Contas de depósito (endereços estáticos) POST /v1/deposit-accounts

`POST /v1/deposit-accounts` com `{"customer_ref": "user_42"}` dá a esse cliente um endereço permanente em cada rede (todas as redes EVM compartilham um endereço). Tudo o que ele enviar é creditado em dólares americanos, com um webhook `deposit.credited` por transferência.

```curl
curl -X POST https://blockwallets.io/v1/deposit-accounts \
  -H "Authorization: Bearer $BW_KEY" -H "Content-Type: application/json" \
  -d '{"customer_ref":"user_42"}'
```

```json
{
  "id": "dac_01J8…",
  "mode": "live",
  "customer_ref": "user_42",
  "deposit_url": "https://blockwallets.io/deposit/dac_01J8…",
  "min_amount": 100,
  "currency": "USD",
  "networks": [
    { "id": "USDT_TRC20", "chain": "tron", "token": "USDT", "address": "TXy4…8kQ2", "min_amount": 100, "confirmations": 1, … },
    { "id": "USDC_BASE", "chain": "base", "token": "USDC", "address": "0x5aE1…", "min_amount": 100, "confirmations": 20, … }
  ]
}
```

- `customer_ref`: 1–128 caracteres entre letras, dígitos e `_ . : @ -`; o mesmo valor sempre retorna a mesma conta. `return_url` opcional nos seus domínios

- A resposta traz `deposit_url` (uma página hospedada com os endereços, QR codes e depósitos recentes) e `networks` (`id`, `address`, `min_amount` e `confirmations` de cada uma)

- Os ids de depósito começam com `dep_`; `amount` é em centavos de dólar, `amount_token` é o valor da moeda, e as moedas cotadas trazem o `rate` usado

Transferências abaixo do mínimo (geralmente $1, BTC $5) são registradas como `below_min` e não são creditadas automaticamente; fale com a gente para creditá-las.

### Webhooks

Enviamos um POST com JSON para a URL de webhook do painel (“Desenvolvedores › Integração e chaves”; Live e Test têm cada um a sua URL e o seu secret). O corpo é o evento:

```http
POST /your/webhook
X-BW-Event: ev_01J8…
X-BW-Delivery: 1
X-BW-Signature: t=1793693052,v1=5f0c9b…

{"id":"ev_01J8…","type":"invoice.paid","created_at":"2026-11-03T08:04:12.000Z","invoice":{"id":"inv_01J8…","ref":"order_1042","status":"paid","amount":4200,…}}
```

- `X-BW-Signature`: `t=<unix seconds>,v1=<hex>`, onde `v1` = HMAC-SHA256(webhook secret desse modo, `t` + `.` + corpo bruto)

- `X-BW-Event`: o id do evento (`ev_…`); desduplique por ele

- `X-BW-Delivery`: o número da tentativa, começando em 1

| Evento | Quando |
| --- | --- |
| `invoice.paid` | A cobrança virou `paid` ou `paid_over` (inclusive dinheiro que chegou depois de expirar) |
| `invoice.wrong_amount` | A cobrança terminou como `wrong_amount` |
| `invoice.closed` | A cobrança expirou sem receber nada, ou você a fechou |
| `deposit.credited` | Uma transferência para uma conta de depósito foi creditada |

Verifique contra o corpo bruto antes de interpretá-lo, rejeite um `t` a mais de 300 segundos do seu relógio e compare em tempo constante. `invoice` / `deposit` no corpo é o mesmo objeto que uma consulta retorna.

```nodejs
import crypto from "node:crypto";

// rawBody: the request body exactly as received (a string), header: X-BW-Signature
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return typeof parts.v1 === "string" && parts.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
```

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 (8 tentativas no total).

### Eventos e reenvio

`GET /v1/events?invoice_id=inv_…` (ou `deposit_id=dep_…`) lista os webhooks de uma cobrança ou depósito com `state`, `attempts`, `last_status` e `last_error`. `POST /v1/events/ev_…/redeliver` o coloca de novo na fila na hora, por exemplo depois de você corrigir o seu endpoint. “Desenvolvedores › Log de webhooks” no painel mostra cada tentativa com requisição e resposta.

### Modo de teste

As cobranças criadas com uma chave `bw_test_` rodam em testnets e nunca tocam dinheiro real. A página de pagamento hospedada mostra um botão “Simular pagamento” (total / metade / a mais), para você acompanhar status e webhooks sem moedas de testnet.
