Desenvolvedores Piloto controlado
OpenAPISuportePainel
API v1 Sem custódia desde o início

Aceite stablecoins.
Mantenha o controle dos fundos.

A MainPay cria faturas com valores exatos, monitora carteiras controladas pelo comerciante, concilia transferências na blockchain e envia webhooks assinados. Teste o fluxo completo sem movimentar fundos reais.

A MainPay nunca custodia fundos.Sem captura, desembolsos, divisão de fundos, depósito em garantia ou reembolsos executados pelo gateway. Os pagamentos são liquidados diretamente em uma carteira controlada pela sua empresa.

Checkout de integração rápida

Crie no servidor. O cliente paga pela própria carteira.

A API REST funciona com fetch nativo, sem SDK. Seu frontend recebe apenas a URL pública do checkout. Nas recargas de créditos, sua aplicação administra e atualiza o saldo após 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.

O checkout hospedado solicita que o pagador assine e transmita a transferência ERC-20 exata pela própria carteira. A MainPay nunca vê uma chave nem detém os fundos ou o saldo de créditos.

Início rápido

Da chave de teste à fatura paga

Antes de começar, conecte uma carteira USDC na Base, crie uma chave mp_test_… e configure um webhook HTTPS público no painel. Os exemplos exigem curl e jq.

01

Configure seu ambiente

Use o modo de teste durante a integração. Os objetos de teste são estruturalmente isolados da liquidação e da contabilidade de produção.

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

Encontre uma carteira de recebimento

Escolha o public_id de uma carteira ativa com o mesmo ativo e rede da fatura que deseja criar.

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

Criar uma fatura

Use uma chave de idempotência única para cada solicitação de criação distinta. Reutilize a mesma chave e o mesmo corpo nas tentativas após falhas 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

Simule a detecção e a liquidação

O simulador utiliza os mesmos serviços de transição e eventos dos pagamentos reais, sem registrar evidências reais na blockchain.

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

Obtenha o estado oficial

Webhooks são avisos. Consulte a fatura sempre que sua integração precisar do estado mais recente.

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

Uma opção prática

Use o SDK Node com tipagem.

Instale @mainpay/node v0.1.0 com npm i @mainpay/node. Ele utiliza a mesma API REST; o exemplo anterior com fetch nativo continua sendo uma integração totalmente compatível.

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);
}

Autenticação

Uma chave, um espaço de trabalho, um modo

mp_test_…

Chaves de teste

Crie faturas de teste isoladas, simule todos os resultados de pagamento e receba eventos livemode:false.

mp_live_…

Chaves de produção

Monitore a liquidação real nas suas próprias carteiras. O acesso à produção só é habilitado após a aprovação do piloto de teste.

http
X-API-Key: mp_test_xxxxxxxxxxxxxxxxxxxxxxxx

As chaves são exibidas uma única vez, armazenadas como hashes, têm permissões delimitadas, podem ser revogadas imediatamente e são isoladas por espaço de trabalho e modo. Nunca as exponha em código do navegador.

Referência de endpoints

Uma API propositalmente enxuta

POST/invoicesCriar uma fatura201
GET/invoicesListar faturas com paginação por cursor200
GET/invoices/{public_id}Obter o estado oficial da fatura200
POST/invoices/{public_id}/cancelCancelar uma fatura aberta200
GET/checkout/{public_id}Obter a visualização de checkout segura para o pagador200
POST/checkout/{public_id}/eventsRegistrar uma etapa anônima do checkout202
GET/walletsListar carteiras de recebimento ativas200
POST/test/invoices/{public_id}/paySimular um pagamento de teste200
Especificação OpenAPI 3.1Baixar YAML →

Comportamento da API

Padrões seguros para operações de pagamento

01

Strings decimais

Valores monetários são sempre serializados como strings, nunca como números de ponto flutuante.

02

Criações idempotentes

Tentativas idênticas retornam a fatura original. Um corpo diferente retorna 409.

03

Paginação por cursor

As listas retornam data, has_more e next_cursor.

04

Limites por chave

100 solicitações por minuto em janela móvel, com picos de até 20 solicitações por segundo.

05

Identificação da solicitação

Toda resposta inclui X-Request-Id para suporte e rastreamento.

06

Checkout seguro para o pagador

O checkout público não inclui e-mail do cliente, IDs internos nem histórico de pagamentos.

Webhooks

Verifique primeiro. Processe uma única vez.

A MainPay assina os bytes exatos do corpo original com t=<unix>,v1=<hmac>. Rejeite timestamps antigos, compare em tempo constante e elimine duplicatas pelo id estável do 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 Ler os bytes originais2 Verificar a assinatura3 Armazenar o ID do evento4 Responder 2xx rapidamente

Tipos de eventos

invoice.createdA fatura está pronta para pagamento
invoice.payment_detectedA transferência correspondente aguarda confirmação
invoice.paidPagamento exato ou aceito confirmado
invoice.underpaidO valor confirmado está abaixo do limite de aceitação
invoice.overpaidO valor confirmado excede o da fatura
invoice.expiredA fatura aberta venceu
invoice.cancelledO comerciante encerrou a cobrança

A entrega ocorre pelo menos uma vez, sem ordem garantida. As novas tentativas usam espera progressiva persistente. A fatura incluída é um retrato do momento; a consulta direta fornece o estado oficial.

Erros

Uma estrutura de resposta previsível

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "not_found",
    "message": "Invoice not found",
    "param": null
  }
}
400Solicitação inválidaCorrija a solicitação antes de tentar novamente.
401AutenticaçãoConfira se a chave API está presente e ativa.
403PermissãoUse o modo e o escopo de permissões corretos da credencial.
404Não encontradoO objeto não existe ou está fora deste espaço de trabalho ou modo.
409ConflitoRepita a solicitação idempotente com a mesma chave e o mesmo corpo.
422ValidaçãoConfira a lista de erros de cada campo.
429Limite de solicitaçõesAguarde o tempo indicado em Retry-After e tente novamente com um atraso aleatório adicional.
503Criação pausadaRespeite Retry-After; as consultas continuam disponíveis.

Piloto controlado

Pronto para testar sua integração?

Conecte uma carteira, configure um webhook e peça à MainPay uma credencial de teste para seu espaço de trabalho. O acesso à produção é liberado após a validação dos testes.

Solicitar acesso ao pilotoAbrir o painel