Ir al contenido

Para desarrolladoresGuía

Recibir pagos con criptomonedas en Node.js

Recibe Bitcoin y USDT en una app Node.js: firma solicitudes a la API con node:crypto, crea un pago, redirige a la página de pago y verifica el webhook en Express.

Una aplicación Node.js recibe cripto con CryptoLike en tres pasos: firma cada solicitud a la API con node:crypto, crea un pago para el pedido y verifica un webhook firmado antes de marcar el pedido como pagado. Basta con Node.js 18 o superior: fetch y crypto vienen incluidos, así que la integración no tiene dependencias aparte de tu framework web.

Para una tienda en un CMS no hay código que escribir: revisa el plugin de WooCommerce y las demás integraciones. Esta guía es para tu propio backend: un SaaS, un marketplace o una app.

Qué necesitas

Una cuenta de CryptoLike, un servidor con Node.js 18+ y una URL pública https:// para los webhooks. Guarda los secretos en el entorno: los ejemplos leen CRYPTOLIKE_API_KEY_ID, CRYPTOLIKE_API_SECRET y CRYPTOLIKE_WEBHOOK_SECRET, además de CRYPTOLIKE_API_URL para la URL de la API de prueba (si no está definida, se usa producción). Desarrolla y prueba primero con claves de prueba y redes de prueba.

1. Crea una clave API de prueba

En el panel abre Desarrolladores → Claves API → Crear clave con los permisos create_invoice y read. Una clave ck_test_ funciona con redes de prueba, donde las monedas no tienen valor. Las claves de prueba se crean en el entorno de prueba: el soporte de CryptoLike te da acceso a su panel, donde creas la misma clave, y la URL de su API (CRYPTOLIKE_API_URL en los ejemplos). El panel de producción emite solo claves reales, y en la red principal una clave de prueba se rechaza con 401 auth.test_key. El secreto se muestra una sola vez; guárdalo en tu gestor de secretos o en el entorno, nunca en el código del frontend.

2. Firma las solicitudes a la API con node:crypto

La firma es un HMAC-SHA256 de cuatro líneas —la marca de tiempo Unix en segundos, el método, la ruta con su query y el SHA-256 del cuerpo— con el secreto de la API como clave. Esta función firma y envía una solicitud:

ts
import { createHash, createHmac } from "node:crypto";

export async function cryptolike(method: string, path: string, payload?: unknown, idempotencyKey?: string) {
  const body = payload === undefined ? "" : JSON.stringify(payload);
  const ts = Math.floor(Date.now() / 1000).toString();
  const canonical = [ts, method.toUpperCase(), path, createHash("sha256").update(body).digest("hex")].join("\n");
  const res = await fetch(`${process.env.CRYPTOLIKE_API_URL ?? "https://api.cryptolike.net"}${path}`, {
    method,
    headers: {
      "X-API-Key": process.env.CRYPTOLIKE_API_KEY_ID!,
      "X-Timestamp": ts,
      "X-Signature": createHmac("sha256", process.env.CRYPTOLIKE_API_SECRET!).update(canonical).digest("hex"),
      "Content-Type": "application/json",
      ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
    },
    body: body || undefined,
  });
  const data = await res.json();
  if (!res.ok) throw new Error(`${data.error?.code ?? res.status}: ${data.error?.message ?? ""}`);
  return data;
}

Serializa el cuerpo una sola vez y envía esa misma cadena: calcular el hash de una cadena y enviar otra es la causa más común de 401 auth.invalid_signature. Compara la función con el vector de referencia de autenticación de la API.

3. Crea el pago de un pedido

Crea el pago en el servidor cuando el cliente hace el pedido. Ponle precio en USD o EUR y deja que el cliente elija la moneda cripto en la página, o pasa un coin_id de GET /v1/currencies. Sin coin_id, la página muestra las monedas disponibles para facturas, cada una con un monto aproximado marcado con ≈; allowed_coin_ids (id con invoice_enabled: true en GET /v1/currencies firmado) limita la lista para este pedido. Con el ID del pedido como Idempotency-Key, un reintento devuelve el mismo pago:

ts
const invoice = await cryptolike("POST", "/v1/invoices", {
  amount: "49.90",
  currency: "USD",
  order_id: order.id,
  idempotency_by_order_id: true,
  return_url: `https://example.com/orders/${order.id}`,
}, `order-${order.id}`);

await orders.saveInvoiceId(order.id, invoice.id);

Mantén los montos como cadenas de punta a punta: "49.90", nunca un número de JavaScript.

4. Envía al cliente a la página de pago

Redirige el navegador a invoice.checkout_url, por ejemplo con res.redirect(303, invoice.checkout_url) en Express. La página de pago muestra el monto, la red, la dirección con código QR, el tiempo restante y el estado en vivo. Para que el cliente siga en tu propio flujo de compra, incrusta la página con el widget de pago; el navegador solo ve el ID de la factura.

5. Verifica el webhook en Express

Agrega un endpoint de webhook en Desarrolladores → Webhooks con los eventos invoice.*. Lee el cuerpo sin procesar —express.json() lo volvería a serializar y rompería la firma—, verifícalo, descarta duplicados por event_id y responde rápido:

ts
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

app.post("/webhooks/cryptolike", express.raw({ type: "application/json" }), async (req, res) => {
  const ts = String(req.header("X-Timestamp") ?? "");
  const got = Buffer.from(String(req.header("X-Signature") ?? ""));
  const secret = process.env.CRYPTOLIKE_WEBHOOK_SECRET ?? "";
  const want = Buffer.from("sha256=" + createHmac("sha256", secret).update(`${ts}.`).update(req.body).digest("hex"));
  const fresh = /^\d+$/.test(ts) && Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  if (!secret || !fresh || got.length !== want.length || !timingSafeEqual(got, want)) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString("utf8"));
  if (await events.firstTime(event.event_id)) {
    if (event.type === "invoice.paid") await orders.markPaid(event.data.invoice.order_id);
  }
  res.sendStatus(200);
});

Cada evento se entrega al menos una vez y puede llegar en desorden: confía en el estado dentro de data, no en el orden de llegada. El procedimiento completo está en cómo verificar los webhooks de pagos cripto.

6. Prueba en una red de prueba y pasa a producción

Crea un pago con la clave de prueba y págalo con monedas de prueba en la red que indica la página. Deberías ver invoice.pending y después invoice.paid. Maneja invoice.underpaid (el cliente puede completar el pago a la misma dirección hasta el vencimiento), invoice.expired (ofrece un pago nuevo) e invoice.reverted (retén la entrega del pedido). Luego cambia a una clave real y sigue la lista de verificación para producción de la guía para desarrolladores.

Errores comunes y cómo corregirlos

Error Qué pasó Qué hacer
401 auth.invalid_signature La cadena firmada no coincide con la solicitud Calcula el hash de la cadena exacta del cuerpo que envías; usa segundos, no los milisegundos de Date.now()
401 auth.replay Dos solicitudes idénticas en el mismo segundo Espera al segundo siguiente y vuelve a firmar
401 en tu webhook El cuerpo se parseó antes de la verificación Usa express.raw() solo en la ruta del webhook
409 conflict El pedido ya tiene un pago por otro monto Usa details.invoice_id; no crees un segundo pago
422 no_allowed_coins Ninguna moneda de allowed_coin_ids está disponible para facturas en esta divisa Envía otros id u omite el campo para ofrecer todas las monedas
429 rate_limited Se superó el límite de solicitudes de la clave (600 por minuto por defecto) Espera los segundos que indica Retry-After

¿Prefieres un SDK?

El SDK oficial de TypeScript para Node.js 18+ envuelve estas llamadas con recursos tipados, reintentos ante 429 y 5xx, un Idempotency-Key en cada POST y verifyWebhook(). No tiene dependencias en tiempo de ejecución y se niega a correr en un navegador, así que el secreto no puede terminar en el código del frontend.

Preguntas frecuentes

¿Puedo crear pagos de CryptoLike desde el navegador?

No. El secreto de la API firma las solicitudes en nombre de tu cuenta, así que debe quedarse en el servidor. Tu frontend le pide a tu backend que cree el pago y recibe solo el ID de la factura o el checkout_url. Por eso el SDK oficial de TypeScript lanza un error cuando detecta un navegador, y el widget de pago solo lee datos públicos del pago.

¿Por qué falla la verificación de la firma del webhook en Express?

Casi siempre porque express.json() parseó el cuerpo antes de la verificación: la firma cubre los bytes originales, y el JSON vuelto a serializar es distinto. Monta express.raw({ type: "application/json" }) en la ruta del webhook, verifica primero y parsea después. Revisa también que el secreto sea el de ese endpoint y que el reloj de tu servidor esté sincronizado.

¿Tengo que consultar el estado del pago periódicamente?

No. Los webhooks informan cada cambio de estado, firmados y con reintentos durante unas 31 horas si tu servidor no responde. Consultar GET /v1/invoices/{id} cada pocos segundos también funciona, por ejemplo en la página a la que vuelve el cliente, pero entrega los pedidos con el webhook invoice.paid verificado, no con un evento del navegador.

Términos usados aquí

Sigue leyendo

Empieza con un pago de prueba

Crea una cuenta, pide a soporte el entorno de prueba y mira cómo un pago pasa de pending a paid en una red de prueba.