Developers
Sticky wallets
Give each of your customers their own deposit addresses that never change, one per network. Every deposit is added to your Crypto Merchant balance, and your server is told which customer sent it, so you can top up the right account.
By invitation
403 sticky_wallets_unavailable. They use the same base URL, secret keys and error format as the rest of the API.Before you start
- 1
Finish setup and choose your coins. Each customer gets an address on the networks of the coins you accept (portal, Settings).
- 2
Create a secret key. In the portal open Developers and create an API key. It is shown once: keep it on your server, for example as
CM_SECRET_KEY, never in a web page or an app. - 3
Add a webhook endpoint. Also under Developers, add your URL with the
deposit.pending,deposit.confirmedanddeposit.droppedevents (or all events). Keep its signing secret,whsec_…, to check each request.
How it works
- 1
A customer opens the deposit page on your site.
- 2
Your server calls
POST /sticky-walletswith your own id for that customer. The first call makes the addresses; every later call returns the same ones. - 3
You show the address, with a QR code, for the coin the customer picks.
- 4
When the coins arrive you get
deposit.pending, thendeposit.confirmedonce the network has confirmed them. - 5
On
deposit.confirmedyou credit the customer named insticky_wallet.reference.
Get a customer's wallet
POST/sticky-wallets
Makes the customer's wallet on the first call and returns the same one on every later call: 201 when it was just made, 200 after that. It is safe to call every time the deposit page opens.
Body parameters
referenceRequiredstring- Your id for this customer: 1 to 120 letters, digits or
. _ : @ + -, starting with a letter or digit. One wallet per reference, for good. labelOptionalstring- A name you'll see in the portal. Sending it again replaces it.
metadataOptionalobject- An object of your own, up to 4 KB. Sending it again replaces it.
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: [...] }, …]
}The sticky wallet object
idstring- The wallet's id, starting with
sw_. referencestring- Your id for the customer.
addressesarray- One entry per network:
network(tron,evm,sol,btc,ltcordoge), theaddressand thecoinsit takes. Theevmaddress is the same on Ethereum, BNB Smart Chain, Polygon, Base and Arbitrum. pending_networksarray- Networks whose address is still being prepared, which is rare. Call again in a few minutes and they appear in
addresses. created_atstring- When the wallet was made.
{
"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"
}Tell customers what to send
Find wallets
GET/sticky-wallets
GET/sticky-wallets/{id}
Look a wallet up by your reference, fetch one by its id, or page through all of them, newest first: limit from 1 to 100, starting_after the last wallet id you have, and has_more tells you whether there are more.
# 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
}List deposits
GET/deposits
Every deposit to your customers' wallets, newest first. Filter by sticky_wallet (a wallet id) and status (pending, confirmed or dropped), and page with limit and starting_after (a deposit id). Use it to catch up when your server missed webhooks.
curl "https://cryptomerchant.online/api/v1/deposits?sticky_wallet=sw_8Kd2pQx7Vb1mNc3T&status=confirmed&limit=20" \
-H "Authorization: Bearer $CM_SECRET_KEY"The deposit object
idstring- The deposit's id. Use it to credit each deposit only once.
statusstringpending: seen on the network, waiting for confirmations.confirmed: final, and added to your balance.dropped: it never confirmed.sticky_walletobject- The wallet it was sent to:
idand yourreference. coinobject- The coin and its network.
amountstring- What the customer sent, in the coin.
feestring- The fee, in the coin.
net_amountstring- What was added to your balance (amount minus fee), once confirmed.
amount_usdstring- Its value in USD when it arrived.
tx_hashstring- The transaction hash;
explorer_urlopens it on a block explorer.
{
"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"
}Webhooks
Deposit events carry the deposit object in data. Check the signature, as shown under Webhooks, before you trust one.
deposit.pending- A deposit to one of your customers' sticky wallets was seen on-chain and is waiting for confirmations.
deposit.confirmed- A sticky wallet deposit confirmed and was added to your balance, minus the fee: credit that customer now.
deposit.dropped- A sticky wallet deposit that was seen never confirmed (failed or removed from the chain).
import express from "express";
import { verifyWebhook } from "./verify-webhook.js"; // from 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);
}
res.sendStatus(200); // reply first, then do the work
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: skip it if you have already stored deposit.id
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,
});
}
},
);Errors
400invalid_request: a field is missing or wrong; the message says which.401unauthorized: the secret key is missing or not valid.403sticky_wallets_unavailable: sticky wallets aren't switched on for this business, or its setup isn't finished.404not_found: no sticky wallet with that id.
Good practice
- Credit a customer only on
deposit.confirmed, and only once per depositid: a webhook can arrive more than once. - Decide who pays the fee: credit
net_amountto pass it on, oramountto cover it yourself. - Your reference is the key: calling
POST /sticky-walletsagain is always safe, so you don't have to store the addresses. - Keep the secret key on your server. If it leaks, revoke it under Developers and create a new one.
- If your endpoint was down, list the
confirmeddeposits and credit the ones you haven't handled yet.