Разработчикам
Постоянные кошельки
Дайте каждому клиенту собственные адреса для пополнения, которые никогда не меняются, по одному на сеть. Каждое пополнение зачисляется на ваш баланс в Crypto Merchant, а ваш сервер узнаёт, какой клиент его отправил, чтобы вы зачислили средства на нужный аккаунт.
Как подключить постоянные кошельки
403 sticky_wallets_unavailable. Они используют тот же базовый URL, те же секретные ключи и тот же формат ошибок, что и остальной API.Перед началом
- 1
Завершите настройку и выберите монеты. Каждый клиент получает адрес в сетях тех монет, которые вы принимаете (личный кабинет, Настройки).
- 2
Создайте секретный ключ. В личном кабинете откройте раздел Разработчикам и создайте API-ключ. Он показывается один раз: храните его на своём сервере, например в
CM_SECRET_KEY, и никогда не размещайте на веб-странице или в приложении. - 3
Добавьте эндпоинт вебхука. Там же, в разделе Разработчикам, добавьте свой URL с событиями
deposit.pending,deposit.confirmedиdeposit.dropped(или со всеми событиями). Сохраните его секрет подписиwhsec_…, чтобы проверять каждый запрос.
Как это работает
- 1
Клиент открывает страницу пополнения на вашем сайте.
- 2
Ваш сервер вызывает
POST /sticky-walletsс вашим собственным ID этого клиента. Первый вызов создаёт адреса, каждый следующий возвращает те же самые. - 3
Вы показываете адрес с QR-кодом для монеты, которую выбрал клиент.
- 4
Когда монеты поступают, вы получаете
deposit.pending, а после подтверждения сетью —deposit.confirmed. - 5
По событию
deposit.confirmedвы зачисляете средства клиенту, указанному вsticky_wallet.reference.
Получение кошелька клиента
POST/sticky-wallets
При первом вызове создаёт кошелёк клиента, а при каждом следующем возвращает тот же: 201, если он только что создан, и 200 после этого. Его можно смело вызывать каждый раз, когда открывается страница пополнения.
Параметры тела
referenceОбязательныйstring- Ваш ID этого клиента: от 1 до 120 букв, цифр или
. _ : @ + -, начинается с буквы или цифры. Один кошелёк на идентификатор, навсегда. labelНеобязательныйstring- Имя, которое вы увидите в личном кабинете. При повторной отправке заменяется.
metadataНеобязательныйobject- Ваш собственный объект размером до 4 КБ. При повторной отправке заменяется.
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: [...] }, …]
}Объект постоянного кошелька
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- Когда кошелёк был создан.
{
"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 показывает, есть ли ещё.
# 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 пополнения). Так можно наверстать пропущенное, если ваш сервер не получил вебхуки.
curl "https://cryptomerchant.online/api/v1/deposits?sticky_wallet=sw_8Kd2pQx7Vb1mNc3T&status=confirmed&limit=20" \
-H "Authorization: Bearer $CM_SECRET_KEY"Объект пополнения
idstring- ID пополнения. Используйте его, чтобы зачислять каждое пополнение только один раз.
statusstringpending: замечено в сети, ожидает подтверждений.confirmed: окончательно и зачислено на ваш баланс.dropped: так и не подтвердилось.sticky_walletobject- Кошелёк, на который оно отправлено:
idи вашreference. coinobject- Монета и её сеть.
amountstring- Сколько отправил клиент, в монете.
feestring- Комиссия, в монете.
net_amountstring- Сколько зачислено на ваш баланс (сумма минус комиссия) после подтверждения.
amount_usdstring- Его стоимость в долларах США на момент поступления.
tx_hashstring- Хеш транзакции;
explorer_urlоткрывает её в обозревателе блоков.
{
"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"
}Вебхуки
События пополнений передают объект пополнения в data. Прежде чем доверять событию, проверьте подпись, как описано в разделе Вебхуки.
deposit.pending- Пополнение постоянного кошелька одного из ваших клиентов замечено в блокчейне и ожидает подтверждений.
deposit.confirmed- Пополнение постоянного кошелька подтверждено и зачислено на ваш баланс за вычетом комиссии: зачислите средства этому клиенту.
deposit.dropped- Замеченное пополнение постоянного кошелька так и не подтвердилось (транзакция не прошла или была удалена из блокчейна).
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]);
}
}Ошибки
400invalid_request: поле отсутствует или некорректно; в сообщении указано, какое именно.401unauthorized: секретный ключ отсутствует или недействителен.403sticky_wallets_unavailable: постоянные кошельки не включены для этого бизнеса или его настройка не завершена.404not_found: постоянного кошелька с таким ID нет.
Рекомендации
- Зачисляйте средства клиенту только по
deposit.confirmedи только один раз для каждогоidпополнения: вебхук может прийти больше одного раза. - Решите, кто платит комиссию: зачисляйте
net_amount, чтобы переложить её на клиента, илиamount, чтобы взять её на себя. - Всё держится на вашем идентификаторе: повторный вызов
POST /sticky-walletsвсегда безопасен, поэтому хранить адреса не нужно. - Храните секретный ключ на своём сервере. Если он утёк, отзовите его в разделе Разработчикам и создайте новый.
- Если ваш эндпоинт был недоступен, получите список пополнений со статусом
confirmedи зачислите те, которые вы ещё не обработали. - Создаёте баланс для клиентов? Прочитайте, как добавить пополнение криптовалютой в аккаунты клиентов.