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
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
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
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
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.confirmedvà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
Khách hàng mở trang nạp tiền trên trang web của bạn.
- 2
Máy chủ của bạn gọi
POST /sticky-walletsvớ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
Bạn hiển thị địa chỉ, kèm mã QR, cho coin mà khách hàng chọn.
- 4
Khi coin đến, bạn nhận
deposit.pending, rồideposit.confirmedkhi mạng đã xác nhận. - 5
Khi nhận
deposit.confirmed, bạn cộng tiền cho khách hàng được nêu trongsticky_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 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" }
}'// 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,btchoặcltc),addressvà cáccoinsmà địa chỉ nhận. Địa chỉevmgiố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.
{
"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ì
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.
# 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"{
"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 "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.
statusstringpending: đã 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:
idvàreferencecủ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_urlmở giao dịch trên trình khám phá khối.
{
"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).
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
400invalid_request: một trường bị thiếu hoặc sai; thông báo sẽ nêu trường nào.401unauthorized: khóa bí mật bị thiếu hoặc không hợp lệ.403sticky_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.404not_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ỗiidkhoả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ặcamountđể tự chịu phí. - Mã tham chiếu của bạn là chìa khóa: gọi lại
POST /sticky-walletsluô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
confirmedvà 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.