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.
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.
// 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.
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.
export MAINPAY_API_BASE=https://api.mainpay.com/v1
export MAINPAY_API_KEY=mp_test_xxxxxxxxxxxxxxxxxxxxxxxx
export MAINPAY_WALLET_ID=1de70609-a510-4fb9-8691-61a8d2c21a34Encontre uma carteira de recebimento
Escolha o public_id de uma carteira ativa com o mesmo ativo e rede da fatura que deseja criar.
curl --fail-with-body -s "$MAINPAY_API_BASE/wallets" \
-H "X-API-Key: $MAINPAY_API_KEY" | jqCriar 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.
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/…" }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.
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"}' | jqObtenha o estado oficial
Webhooks são avisos. Consulte a fatura sempre que sua integração precisar do estado mais recente.
curl --fail-with-body -s \
"$MAINPAY_API_BASE/invoices/$INVOICE_ID" \
-H "X-API-Key: $MAINPAY_API_KEY" | jqUma 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.
// 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.
X-API-Key: mp_test_xxxxxxxxxxxxxxxxxxxxxxxxAs 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
/invoicesCriar uma fatura201/invoicesListar faturas com paginação por cursor200/invoices/{public_id}Obter o estado oficial da fatura200/invoices/{public_id}/cancelCancelar uma fatura aberta200/checkout/{public_id}Obter a visualização de checkout segura para o pagador200/checkout/{public_id}/eventsRegistrar uma etapa anônima do checkout202/walletsListar carteiras de recebimento ativas200/test/invoices/{public_id}/paySimular um pagamento de teste200Comportamento da API
Padrões seguros para operações de pagamento
Strings decimais
Valores monetários são sempre serializados como strings, nunca como números de ponto flutuante.
Criações idempotentes
Tentativas idênticas retornam a fatura original. Um corpo diferente retorna 409.
Paginação por cursor
As listas retornam data, has_more e next_cursor.
Limites por chave
100 solicitações por minuto em janela móvel, com picos de até 20 solicitações por segundo.
Identificação da solicitação
Toda resposta inclui X-Request-Id para suporte e rastreamento.
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.
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.createdA fatura está pronta para pagamentoinvoice.payment_detectedA transferência correspondente aguarda confirmaçãoinvoice.paidPagamento exato ou aceito confirmadoinvoice.underpaidO valor confirmado está abaixo do limite de aceitaçãoinvoice.overpaidO valor confirmado excede o da faturainvoice.expiredA fatura aberta venceuinvoice.cancelledO comerciante encerrou a cobrançaA 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
{
"error": {
"type": "invalid_request_error",
"code": "not_found",
"message": "Invoice not found",
"param": null
}
}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.