Desarrolladores
Billeteras fijas
Da a cada uno de tus clientes sus propias direcciones de depósito, que nunca cambian, una por red. Cada depósito se suma a tu saldo de Crypto Merchant, y tu servidor recibe un aviso de qué cliente lo envió, para que recargues la cuenta correcta.
Cómo obtener billeteras fijas
403 sticky_wallets_unavailable. Usan la misma URL base, las mismas claves secretas y el mismo formato de errores que el resto de la API.Antes de empezar
- 1
Termina la configuración y elige tus monedas. Cada cliente recibe una dirección en las redes de las monedas que aceptas (portal, Configuración).
- 2
Crea una clave secreta. En el portal, abre Desarrolladores y crea una clave de API. Se muestra una sola vez: guárdala en tu servidor, por ejemplo como
CM_SECRET_KEY, y nunca en una página web ni en una app. - 3
Agrega un endpoint de webhook. También en Desarrolladores, agrega tu URL con los eventos
deposit.pending,deposit.confirmedydeposit.dropped(o todos los eventos). Guarda su secreto de firma,whsec_…, para verificar cada solicitud.
Cómo funciona
- 1
Un cliente abre la página de depósito de tu sitio.
- 2
Tu servidor llama a
POST /sticky-walletscon tu propio ID para ese cliente. La primera llamada crea las direcciones; cada llamada posterior devuelve las mismas. - 3
Muestras la dirección, con un código QR, de la moneda que elija el cliente.
- 4
Cuando llegan las monedas, recibes
deposit.pendingy luegodeposit.confirmedcuando la red las confirma. - 5
Con
deposit.confirmed, acreditas el saldo al cliente indicado ensticky_wallet.reference.
Obtener la billetera de un cliente
POST/sticky-wallets
Crea la billetera del cliente en la primera llamada y devuelve la misma en cada llamada posterior: 201 cuando se acaba de crear y 200 después. Puedes llamarlo sin problema cada vez que se abre la página de depósito.
Parámetros del cuerpo
referenceObligatoriostring- Tu ID para este cliente: de 1 a 120 letras, números o
. _ : @ + -, que empiece con una letra o un número. Una billetera por referencia, para siempre. labelOpcionalstring- Un nombre que verás en el portal. Si lo vuelves a enviar, se reemplaza.
metadataOpcionalobject- Un objeto propio, de hasta 4 KB. Si lo vuelves a enviar, se reemplaza.
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: [...] }, …]
}El objeto billetera fija
idstring- El ID de la billetera, que empieza con
sw_. referencestring- Tu ID para el cliente.
addressesarray- Una entrada por red:
network(tron,evm,sol,btcoltc), laaddressy lascoinsque acepta. La direcciónevmes la misma en Ethereum, BNB Smart Chain, Polygon, Base y Arbitrum. pending_networksarray- Redes cuya dirección todavía se está preparando, algo poco frecuente. Vuelve a llamar en unos minutos y aparecerán en
addresses. created_atstring- Cuándo se creó la billetera.
{
"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"
}Indica a tus clientes qué enviar
Buscar billeteras
GET/sticky-wallets
GET/sticky-wallets/{id}
Busca una billetera por tu referencia, obtén una por su ID o recorre todas, de la más reciente a la más antigua: limit de 1 a 100, starting_after con el ID de la última billetera que tienes, y has_more indica si hay más.
# 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
}Listar depósitos
GET/deposits
Todos los depósitos en las billeteras de tus clientes, del más reciente al más antiguo. Filtra por sticky_wallet (un ID de billetera) y status (pending, confirmed o dropped), y pagina con limit y starting_after (un ID de depósito). Úsalo para ponerte al día cuando tu servidor no recibió algunos webhooks.
curl "https://cryptomerchant.online/api/v1/deposits?sticky_wallet=sw_8Kd2pQx7Vb1mNc3T&status=confirmed&limit=20" \
-H "Authorization: Bearer $CM_SECRET_KEY"El objeto depósito
idstring- El ID del depósito. Úsalo para acreditar cada depósito una sola vez.
statusstringpending: detectado en la red, esperando confirmaciones.confirmed: definitivo y sumado a tu saldo.dropped: nunca se confirmó.sticky_walletobject- La billetera a la que se envió:
idy tureference. coinobject- La moneda y su red.
amountstring- Lo que envió el cliente, en la moneda.
feestring- La comisión, en la moneda.
net_amountstring- Lo que se sumó a tu saldo (monto menos comisión), una vez confirmado.
amount_usdstring- Su valor en dólares estadounidenses al momento de llegar.
tx_hashstring- El hash de la transacción;
explorer_urlla abre en un explorador de bloques.
{
"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
Los eventos de depósito llevan el objeto depósito en data. Verifica la firma, como se explica en Webhooks, antes de confiar en uno.
deposit.pending- Se detectó en la blockchain un depósito en la billetera fija de uno de tus clientes y está esperando confirmaciones.
deposit.confirmed- Se confirmó un depósito en una billetera fija y se sumó a tu saldo, descontando la comisión: acredítaselo ahora a ese cliente.
deposit.dropped- Un depósito detectado en una billetera fija nunca se confirmó (falló o se eliminó de la blockchain).
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]);
}
}Errores
400invalid_request: falta un campo o es incorrecto; el mensaje indica cuál.401unauthorized: la clave secreta falta o no es válida.403sticky_wallets_unavailable: las billeteras fijas no están activadas para este negocio, o su configuración no está terminada.404not_found: no hay ninguna billetera fija con ese ID.
Buenas prácticas
- Acredita a un cliente solo con
deposit.confirmed, y solo una vez poridde depósito: un webhook puede llegar más de una vez. - Decide quién paga la comisión: acredita
net_amountpara trasladarla al cliente, oamountpara asumirla tú. - Tu referencia es la clave: volver a llamar a
POST /sticky-walletssiempre es seguro, así que no necesitas guardar las direcciones. - Guarda la clave secreta en tu servidor. Si se filtra, revócala en Desarrolladores y crea una nueva.
- Si tu endpoint estuvo caído, lista los depósitos
confirmedy acredita los que todavía no procesaste. - ¿Vas a crear un saldo para tus clientes? Consulta cómo añadir recargas en cripto a las cuentas de clientes.