# REST API native blockwallets

> REST API native blockwallets: kunci Bearer, invoice dalam sen AS dengan ref idempoten, akun deposit, webhook bertanda tangan HMAC-SHA256, events, dan pengiriman ulang.
>
> HTML: https://blockwallets.io/docs/api?lang=id · English: https://blockwallets.io/docs/api.md?lang=en

## REST API native

API native adalah antarmuka REST milik blockwallets sendiri: invoice berharga sen AS, `ref` idempoten untuk setiap pesanan, akun deposit statis yang mencakup semua jaringan, dan webhook bertanda tangan HMAC-SHA256. API ini membuat tagihan yang sama di halaman pembayaran yang sama dengan [API kompatibel Cryptomus](https://blockwallets.io/docs/cryptomus?lang=id), dengan biaya dan saldo yang sama.

Buat kunci Anda sendiri di dasbor pada “Developer › Integrasi & kunci”: satu kunci Live dan satu kunci Test beserta webhook signing secret-nya. Masing-masing hanya ditampilkan sekali; daftar hanya menampilkan awalannya. Jika akun Anda mengaktifkan verifikasi dua langkah, membuat atau merotasi kunci Live akan meminta kode. Belum punya akun merchant? [Minta akses](https://blockwallets.io/?lang=id#join). Sudah memakai Cryptomus? Lihat [API kompatibel Cryptomus](https://blockwallets.io/docs/cryptomus?lang=id) ([cara memilih](https://blockwallets.io/docs?lang=id#which)).

### Kunci dan permintaan

Base URL `https://blockwallets.io`. Permintaan dan respons berupa JSON. Setiap permintaan membawa:

- `Authorization: Bearer bw_live_…` atau `Bearer bw_test_…`: awalan menentukan mode, jadi kunci Test hanya melihat invoice Test dan berjalan di testnet

- `Content-Type: application/json` pada permintaan `POST`

- Simpan kunci hanya di server Anda, jangan pernah di halaman web atau aplikasi; kami hanya menyimpan hash-nya

Error mengembalikan status HTTP dan `{"error": {"code": "…", "message": "…"}}`: 400 `invalid_request` (field hilang atau formatnya salah), 401 `unauthorized` (kunci tidak ada, tidak dikenal, atau dinonaktifkan), 404 `not_found`, 409 `conflict` (permintaan bertentangan dengan status invoice saat ini), 422 `currency_unsupported`.

### Endpoint

| Endpoint | Fungsinya |
| --- | --- |
| `GET /v1/networks` | Jaringan yang bisa Anda terima saat ini pada mode kunci ini: minimum, konfirmasi, perkiraan waktu tiba, dan perkiraan biaya; koin berharga menyertakan kurs saat ini |
| `POST /v1/invoices` | Membuat invoice (201), atau mengembalikan yang sudah ada untuk `ref` yang sama (200) |
| `GET /v1/invoices?ref=…` | Mencari invoice berdasarkan `ref` Anda |
| `GET /v1/invoices/:id` | Mencari invoice berdasarkan id |
| `POST /v1/invoices/:id/network` | Memilih sendiri jaringan untuk invoice (halaman pembayaran white-label): mengembalikan `address` dan `pay_amount` untuk ditampilkan |
| `POST /v1/invoices/:id/close` | Menutup invoice tertunda yang belum menerima transfer apa pun |
| `POST /v1/deposit-accounts` | Membuka (201) atau mengambil (200) akun deposit satu pelanggan: alamat permanen di setiap jaringan |
| `GET /v1/deposit-accounts?customer_ref=…` | Mencari akun deposit berdasarkan `customer_ref` |
| `GET /v1/deposit-accounts/:id` | Mencari akun deposit berdasarkan id |
| `GET /v1/deposits` | Menampilkan daftar deposit satu akun (`account_id` atau `customer_ref`, `limit` 1–200, default 50) |
| `GET /v1/deposits/:id` | Mencari satu deposit |
| `GET /v1/events` | Pengiriman webhook untuk satu invoice atau deposit (`invoice_id` atau `deposit_id`) |
| `POST /v1/events/:id/redeliver` | Mengirim ulang webhook (202) |

Tambahkan `?probe=1` pada pencarian invoice atau akun deposit untuk memeriksa chain saat itu juga (paling banyak sekali per 10 detik); jika tidak, Anda mendapat status terbaru dari pemindai kami, yang memeriksa setiap menit.

### Membuat invoice POST /v1/invoices

Panggil `POST /v1/invoices` saat pelanggan checkout dan arahkan mereka ke `pay_url` dalam respons.

```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}}'
```

| Field | Wajib | Deskripsi |
| --- | --- | --- |
| `ref` | Ya | Nomor pesanan Anda: 1–64 karakter berupa huruf, angka, dan `_ . : -`. Unik per mode; mengirim `ref` yang sama dengan field yang sama lagi mengembalikan invoice yang sama, dengan field berbeda mengembalikan 409 |
| `amount` | Ya | Jumlah dalam sen AS sebagai bilangan bulat: `4200` = $42.00. Tidak di bawah minimum jaringan setelah jaringan dipilih |
| `title` | Ya | Yang dilihat pelanggan di halaman pembayaran, maksimal 64 karakter |
| `return_url` | Ya | Tujuan tombol “Kembali” di halaman pembayaran; harus berada di salah satu domain situs Anda |
| `success_url` | Tidak | Tujuan pelanggan setelah membayar; defaultnya `return_url` |
| `network` | Tidak | Pilih lebih dulu id jaringan dari [tabel jaringan](https://blockwallets.io/docs/api?lang=id#networks); kosongkan agar pelanggan memilih di halaman pembayaran |
| `customer_ref` | Tidak | Id pelanggan Anda, maksimal 64 karakter, dikembalikan apa adanya |
| `lifetime` | Tidak | Masa berlaku dalam detik, 600 – 43200, default 3600 |
| `amount_rule` | Tidak | `"any"` = kreditkan berapa pun yang masuk (top-up): `paid` segera setelah terkonfirmasi. Kosongkan untuk memakai “Aturan jumlah pembayaran” di “Pengaturan › Aturan pembayaran & tarif” pada dasbor |
| `accuracy_percent` | Tidak | Seberapa besar kekurangan bayar yang masih dianggap lunas, 0 – 5 (persen), default 0 |
| `subtract_percent` | Tidak | Bagian biaya platform yang dibayar pelanggan, 0 – 100 (persen), ditambahkan ke `pay_amount`; kosongkan untuk memakai default dasbor |
| `payment_multiple` | Tidak | Apakah pembayaran yang kurang boleh dilengkapi ke alamat yang sama, default dari dasbor (aktif kecuali Anda ubah); `false`: transfer pertama yang kurang berakhir sebagai `wrong_amount` |
| `meta` | Tidak | Objek JSON apa pun hingga 2 KB, dikembalikan apa adanya dalam pencarian dan webhook |
| `currency` | Tidak | Hanya `USD` (default); selain itu mengembalikan 422 |

Setiap URL harus berada di salah satu domain situs Anda (dasbor → “Developer › Integrasi & kunci”). Responsnya adalah objek invoice di bawah; `network`, `address`, dan `pay_amount` terisi setelah jaringan dipilih.

### Objek invoice

Id diawali `inv_`. `amount` dalam sen AS; `pay_amount` dan `paid_amount` adalah jumlah koin sebagai string desimal pada jaringan yang dipilih.

```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 | Artinya | Yang perlu Anda lakukan |
| --- | --- | --- |
| `pending` | Menunggu pembayaran | — |
| `confirming` | Transfer terlihat dan sedang dikonfirmasi, atau datang kurang dan masih bisa dilengkapi | — |
| `paid` | Lunas | Kirim barang / kreditkan pesanan |
| `paid_over` | Lunas, dan kelebihan bayar | Sama; kelebihannya ada di saldo Anda |
| `wrong_amount` | Berakhir dengan jumlah kurang dari penuh (saat kedaluwarsa, atau pada transfer pertama yang kurang jika `payment_multiple` false) | Bisnis top-up bisa mengkreditkan yang masuk (`paid_amount`; koin berkurs juga punya `paid_cents`); toko meminta pelanggan membayar sisanya |
| `closed` | Kedaluwarsa tanpa dana masuk, atau Anda menutupnya | — (dana yang masuk belakangan tetap dikreditkan dan diberitahukan) |

Field lain: `pay_url` (halaman pembayaran), `payments` (setiap transfer dengan `txid`, `amount`, `confirmations`, dan `confirmed`), `expires_at`, `paid_at`, `closed_at`; BTC, LTC, DOGE, BCH, dan koin native juga membawa `rate` (dolar AS per koin, dikunci 30 menit), `rate_locked_until`, dan `paid_cents` (yang sudah masuk, dalam sen AS).

### Jaringan

Berikan id sebagai `network`. `GET /v1/networks` mengembalikan jaringan yang terbuka saat ini pada mode Anda; jaringan bisa dijeda, jadi lebih baik membacanya daripada menulis nilai secara permanen di kode.

| network | Koin | Chain | Minimum |
| --- | --- | --- | --- |
| `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 |

### Akun deposit (alamat statis) POST /v1/deposit-accounts

`POST /v1/deposit-accounts` dengan `{"customer_ref": "user_42"}` memberi pelanggan itu alamat permanen di setiap jaringan (semua chain EVM memakai satu alamat). Apa pun yang mereka kirim dikreditkan dalam dolar AS, dengan satu webhook `deposit.credited` per transfer.

```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 karakter berupa huruf, angka, dan `_ . : @ -`; nilai yang sama selalu mengembalikan akun yang sama. `return_url` opsional di domain Anda

- Responsnya berisi `deposit_url` (halaman hosting dengan alamat, kode QR, dan deposit terbaru) dan `networks` (`id`, `address`, `min_amount`, `confirmations` untuk tiap jaringan)

- Id deposit diawali `dep_`; `amount` dalam sen AS, `amount_token` jumlah koin, dan koin berharga membawa `rate` yang dipakai

Transfer di bawah minimum (biasanya $1, BTC $5) dicatat sebagai `below_min` dan tidak dikreditkan otomatis; hubungi kami agar dikreditkan.

### Webhook

Kami mengirim POST JSON ke URL webhook di dasbor (“Developer › Integrasi & kunci”; Live dan Test masing-masing punya URL dan secret sendiri). Body-nya adalah event:

```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>`, dengan `v1` = HMAC-SHA256(webhook secret mode tersebut, `t` + `.` + body mentah)

- `X-BW-Event`: id event (`ev_…`); hapus duplikat berdasarkan id ini

- `X-BW-Delivery`: nomor percobaan, mulai dari 1

| Event | Kapan |
| --- | --- |
| `invoice.paid` | Invoice menjadi `paid` atau `paid_over` (termasuk dana yang masuk setelah kedaluwarsa) |
| `invoice.wrong_amount` | Invoice berakhir sebagai `wrong_amount` |
| `invoice.closed` | Invoice kedaluwarsa tanpa dana masuk, atau Anda menutupnya |
| `deposit.credited` | Transfer ke akun deposit telah dikreditkan |

Verifikasi terhadap body mentah sebelum mem-parse-nya, tolak `t` yang selisihnya lebih dari 300 detik dari jam Anda, dan bandingkan dalam waktu konstan. `invoice` / `deposit` di body adalah objek yang sama dengan hasil pencarian.

```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));
}
```

Respons 2xx dianggap terkirim; jika tidak, kami coba lagi setelah 1 menit, 5 menit, 30 menit, 2 jam, 12 jam, 24 jam, dan 24 jam (total 8 percobaan).

### Events dan pengiriman ulang

`GET /v1/events?invoice_id=inv_…` (atau `deposit_id=dep_…`) menampilkan webhook satu invoice atau deposit beserta `state`, `attempts`, `last_status`, dan `last_error`. `POST /v1/events/ev_…/redeliver` langsung mengantrekannya lagi, misalnya setelah Anda memperbaiki endpoint. “Developer › Log webhook” di dasbor menampilkan setiap percobaan beserta permintaan dan responsnya.

### Mode uji

Invoice yang dibuat dengan kunci `bw_test_` berjalan di testnet dan tidak pernah menyentuh uang sungguhan. Halaman pembayaran menampilkan tombol “Simulasikan pembayaran” (penuh / setengah / lebih), sehingga Anda bisa melihat status dan webhook tanpa koin testnet.
