Acepta stablecoins.
Mantén el control de los fondos.
MainPay crea facturas por importes exactos, observa las carteras del comercio, concilia transferencias en cadena y envía webhooks firmados. Prueba el flujo completo sin mover fondos reales.
Integración de pagos
Crea en tu servidor. El cliente paga desde su cartera.
La API REST funciona con fetch nativo, sin SDK. Tu frontend solo recibe la URL pública de pago. Para recargas de créditos, tu aplicación administra y actualiza el saldo tras verificar invoice.paid.
// Server only — MAINPAY_API_KEY never goes to the browser.
const response = await fetch("https://api.mainpay.com/v1/invoices", {
method: "POST",
headers: {
"X-API-Key": process.env.MAINPAY_API_KEY,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json"
},
body: JSON.stringify({
customer_name: "user_42",
description: "2,500 AI credits",
fiat_amount: "25.00",
asset: "USDC",
network: "base",
receiving_account_id: process.env.MAINPAY_WALLET_ID,
client_reference_id: "user_42",
metadata: { credits: 2500 },
purpose: "credit_topup"
})
});
if (!response.ok) throw new Error(`MainPay error: ${response.status}`);
const order = await response.json();
// Return only order.hosted_url to the browser.La página de pago solicita al pagador que firme y transmita la transferencia ERC-20 exacta desde su propia cartera. MainPay nunca ve una clave ni posee los fondos o el saldo de créditos.
Inicio rápido
De la clave de prueba a la factura pagada
Antes de comenzar, conecta una cartera USDC en Base, crea una clave mp_test_… y configura un webhook HTTPS público en el panel. Los ejemplos requieren curl y jq.
Configura tu entorno
Usa el modo de prueba durante la integración. Los objetos de prueba están aislados estructuralmente de la liquidación y contabilidad reales.
export MAINPAY_API_BASE=https://api.mainpay.com/v1
export MAINPAY_API_KEY=mp_test_xxxxxxxxxxxxxxxxxxxxxxxx
export MAINPAY_WALLET_ID=1de70609-a510-4fb9-8691-61a8d2c21a34Encuentra una cartera receptora
Elige el public_id de una cartera activa que coincida con el activo y la red de la factura que vas a crear.
curl --fail-with-body -s "$MAINPAY_API_BASE/wallets" \
-H "X-API-Key: $MAINPAY_API_KEY" | jqCrear una factura
Usa una clave de idempotencia única para cada solicitud de creación distinta. Reutiliza la misma clave y cuerpo al reintentar por errores de transporte.
export IDEMPOTENCY_KEY="quickstart-$(date +%s)-$RANDOM"
ORDER_JSON=$(
curl --fail-with-body -sX POST "$MAINPAY_API_BASE/invoices" \
-H "X-API-Key: $MAINPAY_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg wallet "$MAINPAY_WALLET_ID" '{
description: "API services - quickstart",
fiat_amount: "500.00",
asset: "USDC",
network: "base",
receiving_account_id: $wallet,
customer_name: "Acme Corp",
customer_email: "ap@acme.example",
client_reference_id: "builder-user-42",
metadata: { credits: 2500 },
purpose: "credit_topup"
}')"
)
export INVOICE_ID=$(printf '%s' "$ORDER_JSON" | jq -r '.public_id')
printf '%s\n' "$ORDER_JSON" | jq{ "public_id": "…", "livemode": false, "status": "pending", "crypto_amount": "500.00", "hosted_url": "https://mainpay.com/pay/…" }Simula la detección y liquidación
El simulador utiliza los mismos servicios de transición y eventos que los pagos reales, sin escribir pruebas de actividad real en cadena.
curl --fail-with-body -sX POST \
"$MAINPAY_API_BASE/test/invoices/$INVOICE_ID/pay" \
-H "X-API-Key: $MAINPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scenario":"payment_detected"}' | jq
curl --fail-with-body -sX POST \
"$MAINPAY_API_BASE/test/invoices/$INVOICE_ID/pay" \
-H "X-API-Key: $MAINPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scenario":"exact"}' | jqObtén el estado definitivo
Los webhooks son avisos. Consulta la factura cuando tu integración necesite el estado más reciente.
curl --fail-with-body -s \
"$MAINPAY_API_BASE/invoices/$INVOICE_ID" \
-H "X-API-Key: $MAINPAY_API_KEY" | jqUna opción práctica
Usa el SDK de Node con tipos.
Instala @mainpay/node v0.1.0 con npm i @mainpay/node. Utiliza la misma API REST; el ejemplo anterior con fetch nativo sigue siendo una integración plenamente compatible.
// Optional typed convenience: @mainpay/node v0.1.0
// npm i @mainpay/node
// Server only — MAINPAY_API_KEY never goes to the browser.
import MainPay from "@mainpay/node";
const mainpay = new MainPay(process.env.MAINPAY_API_KEY);
const order = await mainpay.orders.create({
customer_name: "user_42",
description: "2,500 AI credits",
fiat_amount: "25.00",
asset: "USDC",
network: "base",
receiving_account_id: process.env.MAINPAY_WALLET_ID,
client_reference_id: "user_42",
metadata: { credits: 2500 },
purpose: "credit_topup"
});
// Return only order.hosted_url to the browser.
// import { openCheckout } from "@mainpay/node/checkout";
// openCheckout(order.hosted_url);
// In your webhook, verify raw bytes before granting access:
const event = mainpay.webhooks.constructEvent(
rawBody, signatureHeader, process.env.MAINPAY_WEBHOOK_SECRET
);
if (event.type === "invoice.paid") {
await grantCreditsOnce(event.id, event.data.invoice);
}Autenticación
Una clave, un espacio de trabajo, un modo
mp_test_…Claves de prueba
Crea facturas de prueba aisladas, simula todos los resultados de pago y recibe eventos livemode:false.
mp_live_…Claves de producción
Supervisa la liquidación real en tus propias carteras. El acceso a producción se habilita solo tras superar el piloto de prueba.
X-API-Key: mp_test_xxxxxxxxxxxxxxxxxxxxxxxxLas claves se muestran una vez, se guardan como hashes, tienen permisos delimitados, se pueden revocar de inmediato y se aíslan por espacio de trabajo y modo. Nunca las expongas en código del navegador.
Referencia de endpoints
Una API deliberadamente reducida
/invoicesCrear una factura201/invoicesListar facturas con paginación por cursor200/invoices/{public_id}Obtener el estado definitivo de una factura200/invoices/{public_id}/cancelCancelar una factura abierta200/checkout/{public_id}Obtener la vista de pago segura para el pagador200/checkout/{public_id}/eventsRegistrar un paso de pago anónimo202/walletsListar las carteras receptoras activas200/test/invoices/{public_id}/paySimular un pago de prueba200Comportamiento de la API
Valores seguros para las operaciones de pago
Cadenas decimales
Los importes siempre se serializan como cadenas, nunca como números de coma flotante.
Creaciones idempotentes
Los reintentos idénticos devuelven la factura original. Un cuerpo distinto devuelve 409.
Paginación por cursor
Las listas devuelven data, has_more y next_cursor.
Límites por clave
100 solicitudes por minuto móvil, con ráfagas de hasta 20 solicitudes por segundo.
Identidad de la solicitud
Cada respuesta incluye X-Request-Id para soporte y seguimiento.
Pago seguro para el pagador
La página pública de pago excluye el correo del cliente, los ID internos y el historial de pagos.
Webhooks
Verifica primero. Procesa una sola vez.
MainPay firma los bytes exactos del cuerpo original mediante t=<unix>,v1=<hmac>. Rechaza marcas de tiempo antiguas, compara en tiempo constante y elimina duplicados mediante el id estable del evento.
import crypto from "node:crypto";
export function verifyMainPaySignature(rawBody, header, secret) {
try {
const parts = Object.fromEntries(
header.split(",").map((part) => part.split("=", 2))
);
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp) ||
Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = crypto.createHmac("sha256", secret)
.update(`${timestamp}.`).update(rawBody).digest("hex");
const supplied = parts.v1 ?? "";
const a = Buffer.from(expected);
const b = Buffer.from(supplied);
return a.length === b.length && crypto.timingSafeEqual(a, b);
} catch {
return false;
}
}Tipos de eventos
invoice.createdLa factura está lista para el pagoinvoice.payment_detectedLa transferencia coincidente espera confirmacióninvoice.paidPago exacto o aceptado confirmadoinvoice.underpaidEl importe confirmado está por debajo del umbral de aceptacióninvoice.overpaidEl importe confirmado supera el de la facturainvoice.expiredLa factura abierta ha vencidoinvoice.cancelledEl comercio ha detenido el cobroLa entrega se realiza al menos una vez y sin orden garantizado. Los reintentos usan esperas progresivas persistentes. La factura incluida es una instantánea; la consulta directa es la fuente definitiva.
Errores
Una estructura de respuesta predecible
{
"error": {
"type": "invalid_request_error",
"code": "not_found",
"message": "Invoice not found",
"param": null
}
}Piloto controlado
¿Todo listo para probar tu integración?
Conecta una cartera, configura un webhook y solicita a MainPay una credencial de prueba para tu espacio de trabajo. El acceso a producción se habilita tras validar las pruebas.