Desarrolladores Piloto controlado
OpenAPISoportePanel
API v1 Sin custodia desde el diseño

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.

MainPay nunca custodia fondos.Sin captura, desembolsos, reparto, depósito en garantía ni reembolsos ejecutados por la pasarela. Los pagos se liquidan directamente en una cartera que controla tu empresa.

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.

typescript
// 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.

01

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.

bash
export MAINPAY_API_BASE=https://api.mainpay.com/v1
export MAINPAY_API_KEY=mp_test_xxxxxxxxxxxxxxxxxxxxxxxx
export MAINPAY_WALLET_ID=1de70609-a510-4fb9-8691-61a8d2c21a34
02

Encuentra 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.

bash
curl --fail-with-body -s "$MAINPAY_API_BASE/wallets" \
  -H "X-API-Key: $MAINPAY_API_KEY" | jq
03

Crear 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.

bash
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
201{ "public_id": "…", "livemode": false, "status": "pending", "crypto_amount": "500.00", "hosted_url": "https://mainpay.com/pay/…" }
04

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.

bash
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"}' | jq
05

Obtén el estado definitivo

Los webhooks son avisos. Consulta la factura cuando tu integración necesite el estado más reciente.

bash
curl --fail-with-body -s \
  "$MAINPAY_API_BASE/invoices/$INVOICE_ID" \
  -H "X-API-Key: $MAINPAY_API_KEY" | jq

Una 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.

typescript
// 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.

http
X-API-Key: mp_test_xxxxxxxxxxxxxxxxxxxxxxxx

Las 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

POST/invoicesCrear una factura201
GET/invoicesListar facturas con paginación por cursor200
GET/invoices/{public_id}Obtener el estado definitivo de una factura200
POST/invoices/{public_id}/cancelCancelar una factura abierta200
GET/checkout/{public_id}Obtener la vista de pago segura para el pagador200
POST/checkout/{public_id}/eventsRegistrar un paso de pago anónimo202
GET/walletsListar las carteras receptoras activas200
POST/test/invoices/{public_id}/paySimular un pago de prueba200
Especificación OpenAPI 3.1Descargar YAML →

Comportamiento de la API

Valores seguros para las operaciones de pago

01

Cadenas decimales

Los importes siempre se serializan como cadenas, nunca como números de coma flotante.

02

Creaciones idempotentes

Los reintentos idénticos devuelven la factura original. Un cuerpo distinto devuelve 409.

03

Paginación por cursor

Las listas devuelven data, has_more y next_cursor.

04

Límites por clave

100 solicitudes por minuto móvil, con ráfagas de hasta 20 solicitudes por segundo.

05

Identidad de la solicitud

Cada respuesta incluye X-Request-Id para soporte y seguimiento.

06

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.

javascript
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;
  }
}
1 Leer los bytes originales2 Verificar la firma3 Guardar el ID del evento4 Responder 2xx rápidamente

Tipos de eventos

invoice.createdLa factura está lista para el pago
invoice.payment_detectedLa transferencia coincidente espera confirmación
invoice.paidPago exacto o aceptado confirmado
invoice.underpaidEl importe confirmado está por debajo del umbral de aceptación
invoice.overpaidEl importe confirmado supera el de la factura
invoice.expiredLa factura abierta ha vencido
invoice.cancelledEl comercio ha detenido el cobro

La 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

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "not_found",
    "message": "Invoice not found",
    "param": null
  }
}
400Solicitud no válidaCorrige la solicitud antes de reintentar.
401AutenticaciónComprueba que la clave API esté presente y activa.
403PermisoUsa el modo y los permisos correctos de la credencial.
404No encontradoEl objeto no existe o está fuera de este espacio de trabajo o modo.
409ConflictoReintenta una solicitud idempotente con la misma clave y cuerpo.
422ValidaciónRevisa la lista de errores de cada campo.
429Límite de solicitudesEspera el tiempo indicado en Retry-After y reintenta con una demora aleatoria adicional.
503Creación pausadaRespeta Retry-After; las consultas siguen disponibles.

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.

Solicitar acceso al pilotoAbrir el panel