Chuyển đến nội dung
Crypto Merchant

Nhà phát triển

Ví cố định

Cấp cho từng khách hàng của bạn địa chỉ nạp riêng không bao giờ thay đổi, mỗi mạng một địa chỉ. Mọi khoản nạp đều được cộng vào số dư Crypto Merchant của bạn, và máy chủ của bạn được báo khách hàng nào đã gửi, để bạn nạp tiền vào đúng tài khoản.

Cách bật ví cố định

Chọn ví khách hàng khi đăng ký, hoặc đề nghị bộ phận hỗ trợ bật ví cố định cho tài khoản bạn đang có. Khi chưa được bật, các endpoint này trả về 403 sticky_wallets_unavailable. Chúng dùng cùng URL gốc, khóa bí mật và định dạng lỗi như phần còn lại của API.

Trước khi bắt đầu

  1. 1

    Hoàn tất thiết lập và chọn coin. Mỗi khách hàng nhận một địa chỉ trên các mạng của những coin bạn chấp nhận (cổng quản lý, Cài đặt).

  2. 2

    Tạo khóa bí mật. Trong cổng quản lý, mở Nhà phát triển và tạo một khóa API. Khóa chỉ hiển thị một lần: hãy giữ khóa trên máy chủ của bạn, ví dụ dưới dạng CM_SECRET_KEY, đừng bao giờ đặt trong trang web hay ứng dụng.

  3. 3

    Thêm endpoint webhook. Cũng trong mục Nhà phát triển, thêm URL của bạn với các sự kiện deposit.pending, deposit.confirmed và deposit.dropped (hoặc tất cả sự kiện). Giữ lại khóa bí mật ký của endpoint, whsec_…, để kiểm tra từng yêu cầu.

Cách hoạt động

  1. 1

    Khách hàng mở trang nạp tiền trên trang web của bạn.

  2. 2

    Máy chủ của bạn gọi POST /sticky-wallets với ID riêng của bạn cho khách hàng đó. Lần gọi đầu tiên tạo địa chỉ; mọi lần gọi sau đều trả về cùng các địa chỉ đó.

  3. 3

    Bạn hiển thị địa chỉ, kèm mã QR, cho coin mà khách hàng chọn.

  4. 4

    Khi coin đến, bạn nhận deposit.pending, rồi deposit.confirmed khi mạng đã xác nhận.

  5. 5

    Khi nhận deposit.confirmed, bạn cộng tiền cho khách hàng được nêu trong sticky_wallet.reference.

Lấy ví của khách hàng

POST/sticky-wallets

Tạo ví của khách hàng ở lần gọi đầu tiên và trả về cùng ví đó ở mọi lần gọi sau: 201 khi ví vừa được tạo, 200 sau đó. Có thể gọi mỗi lần trang nạp tiền được mở mà không lo vấn đề gì.

Tham số body

referenceBắt buộcstring
ID của bạn cho khách hàng này: từ 1 đến 120 chữ cái, chữ số hoặc . _ : @ + -, bắt đầu bằng chữ cái hoặc chữ số. Mỗi mã tham chiếu một ví, vĩnh viễn.
labelKhông bắt buộcstring
Tên bạn sẽ thấy trong cổng quản lý. Gửi lại sẽ thay thế tên cũ.
metadataKhông bắt buộcobject
Một đối tượng của riêng bạn, tối đa 4 KB. Gửi lại sẽ thay thế đối tượng cũ.
curl
curl https://cryptomerchant.online/api/v1/sticky-wallets \
  -H "Authorization: Bearer $CM_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "user-1042",
    "label": "Jane Doe",
    "metadata": { "plan": "pro" }
  }'
Node.js
// On your server, when a customer opens their deposit page
export async function depositAddresses(userId) {
  const res = await fetch("https://cryptomerchant.online/api/v1/sticky-wallets", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CM_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ reference: String(userId) }),
  });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${error.code}: ${error.message}`);
  }
  const wallet = await res.json();
  return wallet.addresses; // [{ network, address, coins: [...] }, …]
}

Đối tượng ví cố định

idstring
ID của ví, bắt đầu bằng sw_.
referencestring
ID của bạn cho khách hàng.
addressesarray
Mỗi mạng một mục: network (tron, evm, sol, btc hoặc ltc), address và các coins mà địa chỉ nhận. Địa chỉ evm giống nhau trên Ethereum, BNB Smart Chain, Polygon, Base và Arbitrum.
pending_networksarray
Các mạng có địa chỉ vẫn đang được chuẩn bị, điều này hiếm khi xảy ra. Hãy gọi lại sau vài phút và chúng sẽ xuất hiện trong addresses.
created_atstring
Thời điểm ví được tạo.
sticky_wallet
{
  "id": "sw_8Kd2pQx7Vb1mNc3T",
  "object": "sticky_wallet",
  "reference": "user-1042",
  "label": "Jane Doe",
  "metadata": { "plan": "pro" },
  "addresses": [
    {
      "network": "tron",
      "address": "TXbajeGHq4Vb…",
      "coins": [
        { "id": "TRX.TRON", "symbol": "TRX", "network": "Tron" },
        { "id": "USDT.TRON", "symbol": "USDT", "network": "Tron" }
      ]
    },
    {
      "network": "evm",
      "address": "0xE550EC6d…",
      "coins": [
        { "id": "ETH.ETH", "symbol": "ETH", "network": "Ethereum" },
        { "id": "BNB.BSC", "symbol": "BNB", "network": "BNB Smart Chain" },
        { "id": "USDT.BSC", "symbol": "USDT", "network": "BNB Smart Chain" }
      ]
    }
  ],
  "pending_networks": [],
  "created_at": "2026-10-29T18:20:41.000Z"
}

Cho khách hàng biết nên gửi gì

Hiển thị từng địa chỉ cùng các coin được liệt kê cho địa chỉ đó. Coin trên mạng khác, hoặc token không có trong danh sách, sẽ không được cộng tự động: hãy liên hệ hỗ trợ nếu khách hàng gửi những loại này.

Tìm ví

GET/sticky-wallets

GET/sticky-wallets/{id}

Tra cứu ví theo mã tham chiếu của bạn, lấy một ví theo ID, hoặc duyệt qua tất cả, mới nhất trước: limit từ 1 đến 100, starting_after là ID của ví cuối cùng bạn có, và has_more cho biết còn ví nào nữa không.

curl
# By your reference
curl "https://cryptomerchant.online/api/v1/sticky-wallets?reference=user-1042" \
  -H "Authorization: Bearer $CM_SECRET_KEY"

# By id
curl https://cryptomerchant.online/api/v1/sticky-wallets/sw_8Kd2pQx7Vb1mNc3T \
  -H "Authorization: Bearer $CM_SECRET_KEY"

# All of them, 50 at a time
curl "https://cryptomerchant.online/api/v1/sticky-wallets?limit=50&starting_after=sw_8Kd2pQx7Vb1mNc3T" \
  -H "Authorization: Bearer $CM_SECRET_KEY"
Phản hồi
{
  "object": "list",
  "data": [ { "id": "sw_8Kd2pQx7Vb1mNc3T", "object": "sticky_wallet", … } ],
  "has_more": false
}

Liệt kê khoản nạp

GET/deposits

Mọi khoản nạp vào ví của khách hàng, mới nhất trước. Lọc theo sticky_wallet (ID của ví) và status (pending, confirmed hoặc dropped), và phân trang bằng limit và starting_after (ID của khoản nạp). Dùng cách này để bắt kịp khi máy chủ của bạn bỏ lỡ webhook.

curl
curl "https://cryptomerchant.online/api/v1/deposits?sticky_wallet=sw_8Kd2pQx7Vb1mNc3T&status=confirmed&limit=20" \
  -H "Authorization: Bearer $CM_SECRET_KEY"

Đối tượng khoản nạp

idstring
ID của khoản nạp. Dùng ID này để mỗi khoản nạp chỉ được cộng một lần.
statusstring
pending: đã thấy trên mạng, đang chờ xác nhận. confirmed: đã hoàn tất và được cộng vào số dư của bạn. dropped: không bao giờ được xác nhận.
sticky_walletobject
Ví nhận khoản nạp: id và reference của bạn.
coinobject
Coin và mạng của coin.
amountstring
Số tiền khách hàng đã gửi, tính bằng coin đó.
feestring
Phí, tính bằng coin đó.
net_amountstring
Số tiền đã được cộng vào số dư của bạn (số tiền trừ phí), sau khi được xác nhận.
amount_usdstring
Giá trị bằng đô la Mỹ tại thời điểm khoản nạp đến.
tx_hashstring
Mã hash giao dịch; explorer_url mở giao dịch trên trình khám phá khối.
deposit
{
  "id": "cmvu1x9a0000l10u8k2f3h7q",
  "object": "deposit",
  "status": "confirmed",
  "sticky_wallet": { "id": "sw_8Kd2pQx7Vb1mNc3T", "reference": "user-1042" },
  "coin": { "id": "USDT.TRON", "symbol": "USDT", "network": "Tron" },
  "address": "TXbajeGHq4Vb…",
  "amount": "250",
  "amount_usd": "250.00",
  "fee": "2.5",
  "net_amount": "247.5",
  "from_address": "TQn9Y2khEsLJ…",
  "tx_hash": "36423ec5…f00d4a",
  "explorer_url": "https://tronscan.org/#/transaction/36423ec5…f00d4a",
  "confirmations": 19,
  "detected_at": "2026-10-29T18:31:02.000Z",
  "confirmed_at": "2026-10-29T18:32:01.000Z"
}

Webhook

Sự kiện nạp tiền mang đối tượng khoản nạp trong data. Hãy kiểm tra chữ ký như hướng dẫn trong Webhook trước khi tin tưởng sự kiện.

deposit.pending
Đã thấy on-chain một khoản nạp vào ví cố định của một khách hàng của bạn và đang chờ xác nhận.
deposit.confirmed
Một khoản nạp vào ví cố định đã được xác nhận và cộng vào số dư của bạn sau khi trừ phí: hãy cộng tiền cho khách hàng đó ngay.
deposit.dropped
Một khoản nạp vào ví cố định đã thấy nhưng không bao giờ được xác nhận (thất bại hoặc bị loại khỏi chuỗi khối).
server.js · cộng tiền cho khách hàng
import express from "express";
import { verifyWebhook } from "./verify-webhook.js"; // from the Webhooks page
import { db } from "./db.js"; // your database; table webhook_events as on the Webhooks page

const app = express();

app.post(
  "/webhooks/crypto-merchant",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    let event;
    try {
      event = verifyWebhook(req.body, req.get("CM-Signature"), process.env.CM_WEBHOOK_SECRET);
    } catch {
      return res.sendStatus(400);
    }
    try {
      // Save first: a repeat delivery has the same event.id and is ignored
      await db.query(
        "INSERT INTO webhook_events (id, type, payload) VALUES ($1, $2, $3) ON CONFLICT (id) DO NOTHING",
        [event.id, event.type, event],
      );
    } catch {
      return res.sendStatus(500); // not saved: we send it again later
    }
    res.sendStatus(200); // stored, so confirm it
    processEvents().catch(console.error); // or run it from a background job
  },
);

async function processEvents() {
  const { rows } = await db.query(
    "SELECT payload FROM webhook_events WHERE processed_at IS NULL ORDER BY received_at",
  );
  for (const { payload: event } of rows) {
    const deposit = event.data;
    if (event.type === "deposit.pending") {
      // Optional: show "deposit on its way" to deposit.sticky_wallet.reference
    }
    if (event.type === "deposit.confirmed") {
      // Credit once: creditCustomer must skip a deposit.id it has already credited
      await creditCustomer({
        customerId: deposit.sticky_wallet.reference,
        depositId: deposit.id,
        coin: deposit.coin.id, // e.g. "USDT.TRON"
        amount: deposit.net_amount, // or deposit.amount to cover the fee yourself
        txHash: deposit.tx_hash,
      });
    }
    await db.query("UPDATE webhook_events SET processed_at = now() WHERE id = $1", [event.id]);
  }
}

Lỗi

400
invalid_request: một trường bị thiếu hoặc sai; thông báo sẽ nêu trường nào.
401
unauthorized: khóa bí mật bị thiếu hoặc không hợp lệ.
403
sticky_wallets_unavailable: ví cố định chưa được bật cho doanh nghiệp này, hoặc doanh nghiệp chưa hoàn tất thiết lập.
404
not_found: không có ví cố định nào với ID đó.

Thực hành tốt

  • Chỉ cộng tiền cho khách hàng khi nhận deposit.confirmed, và chỉ một lần cho mỗi id khoản nạp: một webhook có thể đến nhiều lần.
  • Quyết định ai trả phí: cộng net_amount để chuyển phí sang khách hàng, hoặc amount để tự chịu phí.
  • Mã tham chiếu của bạn là chìa khóa: gọi lại POST /sticky-wallets luôn an toàn, nên bạn không cần lưu các địa chỉ.
  • Giữ khóa bí mật trên máy chủ của bạn. Nếu khóa bị lộ, hãy thu hồi khóa trong mục Nhà phát triển và tạo khóa mới.
  • Nếu endpoint của bạn từng ngừng hoạt động, hãy liệt kê các khoản nạp confirmed và cộng tiền cho những khoản bạn chưa xử lý.
  • Bạn đang xây dựng số dư cho khách hàng? Hãy đọc cách thêm nạp tiền điện tử vào tài khoản khách hàng.