跳到主要内容
Crypto Merchant

开发者

固定钱包

为您的每位客户提供专属且永不更改的充值地址,每个网络一个。每笔充值都会计入您的 Crypto Merchant 余额,并通知您的服务器是哪位客户发送的,以便您为正确的账户充值。

如何开通固定钱包

注册时选择客户钱包,或请客服为您现有的账户开通固定钱包。开通之前,这些端点会返回 403 sticky_wallets_unavailable。它们与 API 的其他部分使用相同的基础 URL、密钥和错误格式。

开始之前

  1. 1

    完成设置并选择币种。每位客户都会在您接受的币种所在的网络上获得一个地址(后台的设置)。

  2. 2

    创建密钥。在后台打开开发者并创建一个 API 密钥。密钥只显示一次:请将其保存在您的服务器上,例如保存为 CM_SECRET_KEY,切勿放入网页或应用中。

  3. 3

    添加 Webhook 端点。同样在开发者中,添加您的 URL,并选择 deposit.pending、deposit.confirmed 和 deposit.dropped 事件(或所有事件)。请保存其签名密钥 whsec_…,用于校验每个请求。

工作原理

  1. 1

    客户在您的网站上打开充值页面。

  2. 2

    您的服务器使用您为该客户设定的 ID 调用 POST /sticky-wallets。首次调用会创建地址,之后的每次调用都返回相同的地址。

  3. 3

    您为客户选择的币种显示对应的地址和二维码。

  4. 4

    币到账时,您会收到 deposit.pending,网络确认后再收到 deposit.confirmed。

  5. 5

    收到 deposit.confirmed 后,为 sticky_wallet.reference 中指明的客户入账。

获取客户的钱包

POST/sticky-wallets

首次调用时创建客户的钱包,之后每次调用都返回同一个钱包:刚创建时返回 201,之后返回 200。每次打开充值页面时都可以放心调用。

请求体参数

reference必填string
您为该客户设定的 ID:1 到 120 个字母、数字或 . _ : @ + -,以字母或数字开头。每个标识永久对应一个钱包。
label可选string
在后台显示的名称。再次发送会替换原有名称。
metadata可选object
您自定义的对象,最大 4 KB。再次发送会替换原有内容。
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: [...] }, …]
}

固定钱包对象

idstring
钱包 ID,以 sw_ 开头。
referencestring
您为该客户设定的 ID。
addressesarray
每个网络一项:network(tron、evm、sol、btc 或 ltc)、address 以及该地址支持的 coins。evm 地址在 Ethereum、BNB Smart Chain、Polygon、Base 和 Arbitrum 上相同。
pending_networksarray
地址仍在准备中的网络,这种情况很少见。几分钟后再次调用,它们就会出现在 addresses 中。
created_atstring
钱包的创建时间。
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"
}

告诉客户应发送什么

显示每个地址时,请同时列出该地址支持的币种。其他网络上的币或未列出的代币不会自动入账:如果客户发送了这类资产,请联系客服。

查找钱包

GET/sticky-wallets

GET/sticky-wallets/{id}

可按您的标识查找钱包、按 ID 获取单个钱包,或按最新在前的顺序分页浏览全部钱包:limit 为 1 到 100,starting_after 为您已有的最后一个钱包 ID,has_more 表示是否还有更多。

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"
响应
{
  "object": "list",
  "data": [ { "id": "sw_8Kd2pQx7Vb1mNc3T", "object": "sticky_wallet", … } ],
  "has_more": false
}

列出充值

GET/deposits

您客户钱包收到的所有充值,最新的在前。可按 sticky_wallet(钱包 ID)和 status(pending、confirmed 或 dropped)筛选,并用 limit 和 starting_after(充值 ID)分页。当您的服务器漏收 Webhook 时,可用它补齐。

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

充值对象

idstring
充值 ID。用它确保每笔充值只入账一次。
statusstring
pending:已在网络上检测到,正在等待确认。confirmed:已最终确认,并已计入您的余额。dropped:始终未确认。
sticky_walletobject
接收该笔充值的钱包:id 和您的 reference。
coinobject
币种及其网络。
amountstring
客户发送的数额,以该币种计。
feestring
手续费,以该币种计。
net_amountstring
确认后计入您余额的数额(金额减去手续费)。
amount_usdstring
到账时的美元价值。
tx_hashstring
交易哈希;explorer_url 会在区块浏览器中打开该交易。
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

充值事件的 data 中包含充值对象。在信任事件之前,请按 Webhook 中的说明验证签名。

deposit.pending
已在链上检测到一笔向您某位客户的固定钱包的充值,正在等待确认。
deposit.confirmed
一笔固定钱包充值已确认,并在扣除手续费后计入您的余额:现在请为该客户入账。
deposit.dropped
已检测到的一笔固定钱包充值始终未确认(交易失败或已从链上移除)。
server.js · 为客户入账
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]);
  }
}

错误

400
invalid_request:某个字段缺失或有误,错误信息会指明是哪个字段。
401
unauthorized:密钥缺失或无效。
403
sticky_wallets_unavailable:该商家未开通固定钱包,或尚未完成设置。
404
not_found:不存在该 ID 的固定钱包。

最佳实践

  • 只在收到 deposit.confirmed 时为客户入账,并且每个充值 id 只入账一次:同一个 Webhook 可能会多次送达。
  • 决定由谁承担手续费:入账 net_amount 表示由客户承担,入账 amount 表示由您自己承担。
  • 关键在于您的标识:重复调用 POST /sticky-wallets 始终是安全的,因此您无需保存这些地址。
  • 请将密钥保存在您的服务器上。如果密钥泄露,请在开发者中将其撤销并创建新的密钥。
  • 如果您的端点曾经无法访问,请列出 confirmed 状态的充值,并为尚未处理的充值入账。
  • 要为客户建立余额?请阅读如何为客户账户添加加密货币充值。