API Peptide-Pay
Uma API REST pra cobrar cartão e cripto, com settlement pra uma wallet USDC que você controla. Um POST cria um checkout. Um webhook te avisa que pagou. No ar em menos de 30 minutos.
Primeiros passos
O Peptide-Pay tem quatro modos de integração. Escolhe o que bate com suas restrições; a API por baixo é a mesma.
Botão drop-in, sem cadastro. Passa seu endereço de wallet USDC no body da request. Zero estado no backend; público, visível no DevTools.
Server-to-server. Esconde sua wallet atrás de Bearer sk_live_. Configura branding, webhooks, payouts em massa pelo dashboard.
Plugin pronto. Sobe o ZIP, cola sua API key + webhook secret, pedidos auto-completam no pagamento. Pronto pra HPOS, WC 7.0+.
Custom App + Método de Pagamento Manual. ~30 min de instalação em uma loja existente. Marcamos os pedidos como pagos via Shopify Admin API.
Quickstart (5 min)
Três passos: cria uma session, redireciona o cliente, trata o webhook. O exemplo abaixo é uma rota de checkout Node.js pronta pra produção.
// Create a checkout session and redirect your customer.
// Authorization resolves the merchant wallet server-side — no wallet in the body.
const res = await fetch('https://peptide-pay.com/api/v1/checkout/init', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.PEPTIDEPAY_API_KEY}`, // sk_live_…
},
body: JSON.stringify({
amount_cents: 5000, // €50.00 — integer, in cents
currency: 'EUR', // EUR, USD, GBP, CAD, AUD, CHF
email: 'buyer@example.com', // optional, shown in checkout
success_url: 'https://mystore.com/success',
cancel_url: 'https://mystore.com/cart',
webhook_url: 'https://mystore.com/api/peptidepay-webhook',
metadata: { order_id: '1234' },
}),
});
const { id, url, tracking_number } = await res.json();
// => { id: 'cs_abc…', url: 'https://peptide-pay.com/session/cs_abc…',
// tracking_number: '0x…', provider: 'gateway', status: 'pending', … }
// Redirect your customer to the hosted checkout.
return Response.redirect(url, 303);Esse é o happy path inteiro. O cliente cai num checkout hospedado em , escolhe cartão ou cripto, e seu webhook dispara em até 30s do pagamento.
Autenticação
Dois esquemas, dependendo do modo de integração:
| Esquema | Como | Quando |
|---|---|---|
| Bearer token | Authorization: Bearer sk_live_… | Server-side. Mantém a wallet privada. |
| Wallet no body | { "wallet": "0x…", … } | Sites estáticos / widgets sem backend. |
sk_live_… and an sk_test_… key. Use sk_live_ as your canonical key — that is what every example here uses. The sk_test_ key is provided for the webhook-receiver simulator at /api/v1/test/fire-webhook. Peptide-Pay settles real on-chain USDC — there is no test network. To dry-run an integration, run a $1 real payment and refund yourself.Referência da API
Base URL: . Todos os endpoints falam JSON, retornam um único objeto em caso de sucesso e um objeto em 4xx/5xx.
#Cria uma checkout session
Gera uma URL de checkout hospedado. O cliente abre, paga com cartão ou cripto, o Peptide-Pay faz settlement pra sua wallet em USDC, o webhook dispara.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| amount_cents | integer | obrigatório | Valor na menor unidade da moeda (centavos). Range 100 – 10 000 000. |
| currency | string | obrigatório | Código ISO 4217. Suportadas: EUR, USD, GBP, CAD, AUD, CHF. |
| wallet | string | one-of | Wallet USDC na Polygon (0x + 40 hex). Obrigatória se não estiver autenticado por Bearer key. |
| customer_email | string | opcional | Exibido na UI do checkout e passado pra on-ramp pra reuso de KYC. |
| success_url | url | opcional | Redirect após pagamento com sucesso. Só http/https. |
| cancel_url | url | opcional | Redirect se o cliente abandonar o checkout. |
| webhook_url | url | opcional | Alvo do POST pros eventos order.paid. Sobrescreve o default do dashboard. |
| provider | string | opcional | Default 'gateway' (picker inteligente — recomendado). Ou trava num id específico de on-ramp de GET /providers (ex. moonpay, revolut, banxa, transak). |
| product_name | string | opcional | Label exibido na página de checkout (máx 80 chars). |
| metadata | object | opcional | Até 10 pares string key/value, devolvidos no webhook. Key reservada: order_id. |
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| id | string | opcional | Session id, começa com cs_. |
| url | string | opcional | URL de checkout hospedado pra redirecionar o cliente. |
| status | string | opcional | Sempre "pending" na criação. |
| amount | integer | opcional | Echo de amount_cents. |
| currency | string | opcional | Echo de currency. |
| provider | string | opcional | Echo de provider (default 'gateway'). |
| expires_at | string | opcional | Expiração ISO 8601 (24h da criação). |
| tracking_number | string | opcional | Endereço de settlement Polygon — bate com address_in no payload do webhook, usável em /track pra monitoramento ao vivo. |
Exemplos
// Node.js 18+
const res = await fetch('https://peptide-pay.com/api/v1/checkout/init', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.PEPTIDEPAY_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(), // safe double-submit
},
body: JSON.stringify({
amount_cents: 5000,
currency: 'EUR',
email: 'buyer@example.com',
success_url: 'https://mystore.com/success',
cancel_url: 'https://mystore.com/cart',
metadata: { order_id: '1234' },
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { id, url } = await res.json();
return Response.redirect(url, 303);#Busca uma session (polling)
Usa como fallback de webhook, ou pra hidratar uma página de sucesso depois do redirect. Server-side reconsulta nossa camada de settlement a cada chamada — barato (<200ms), então polling a cada 3-5s tá de boa.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| id | string | opcional | Session id. |
| status | string | opcional | pending | paid | expired | failed. |
| amount | integer | opcional | Valor original em centavos. |
| currency | string | opcional | Moeda original. |
| paid_at | string|null | opcional | ISO 8601 de quando o settlement on-chain rolou. |
| paid_provider | string|null | opcional | Qual provider processou de fato (pode diferir do requisitado). |
| txid | string|null | opcional | Txid de settlement Polygon. Link com polygonscan.com/tx/{txid}. |
| expires_at | string | opcional | Expiração ISO 8601. |
// Poll every 3-5 seconds until terminal state. Use webhooks for push-
// delivery in production; polling is the fallback when webhooks are down.
async function waitForPayment(sessionId, { timeoutMs = 15 * 60 * 1000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`https://peptide-pay.com/api/v1/sessions/${sessionId}`);
const s = await res.json();
if (s.status === 'paid') return s; // terminal: success
if (s.status === 'expired') throw new Error('Session expired');
if (s.status === 'failed') throw new Error('Payment failed');
await new Promise(r => setTimeout(r, 4000));
}
throw new Error('Polling timeout');
}#Matriz de providers ao vivo
Lista as on-ramps aceitando tráfego no momento, com mínimos por provider. Cacheada 5 min no edge — faz poll uma vez no boot, não a cada request.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| providers[].id | string | opcional | Chave do provider (passável como `provider` em /checkout/init). |
| providers[].provider_name | string | opcional | Label humano pro dropdown. |
| providers[].status | string | opcional | 'active' (sempre filtrado pra active nesse endpoint). |
| providers[].minimum_currency | string | opcional | Código ISO do mínimo. |
| providers[].minimum_amount | number | opcional | Menor valor que o provider aceita (em unidades de minimum_currency). |
curl -sS 'https://peptide-pay.com/api/v1/providers' | jq '.providers[] | {id, provider_name, minimum_currency, minimum_amount}'
# [
# { "id": "gateway", "provider_name": "Smart (recommended)", "minimum_currency": "USD", "minimum_amount": 1 },
# { "id": "moonpay", "provider_name": "Moonpay", "minimum_currency": "EUR", "minimum_amount": 20 },
# { "id": "revolut", "provider_name": "Revolut Ramp", "minimum_currency": "EUR", "minimum_amount": 10 },
# { "id": "binance", "provider_name": "Binance Pay", "minimum_currency": "EUR", "minimum_amount": 15 },
# …
# ]
# Cache: 5 minutes at the edge. Call once per deploy, not per request.Webhooks
Quando uma session chega num estado terminal, a gente faz POST de um evento JSON assinado pra que você configurou (por session ou no dashboard). Sempre faz parse do body da request pra verificação de assinatura — re-serializar JSON reordena keys e quebra o HMAC.
POST /your-endpoint HTTP/1.1
Host: mystore.com
Content-Type: application/json
x-peptidepay-signature: t=1745300551,v1=3f9b5c1e8a7d… ← HMAC-SHA256, hex
{
"event": "order.paid",
"session_id": "cs_abc123",
"order_id": "1234",
"address_in": "0xAb12…",
"status": "paid",
"amount": 5000,
"currency": "EUR",
"txid": "0xfa89b2…",
"paid_at": "2026-04-23T10:02:31.000Z",
"attempt": 1
}Tipos de evento
| Evento | Quando |
|---|---|
| order.paid | Settlement on-chain confirmado. + + garantidos. Marca pedido como pago. |
Hoje só é entregue — sessions expired e failed são observáveis via (status vira depois do TTL de 24h; falhas terminais viram ). Podemos adicionar push pra esses numa versão futura.
Verificação de assinatura
Merchants com conta cadastrada recebem um secret e toda entrega carrega um header no formato . Calcula e faz compare de tempo constante com . Rejeita qualquer coisa mais velha que 5 minutos.
// Node.js — Express/Next.js route handler
import crypto from 'node:crypto';
const SECRET = process.env.PEPTIDEPAY_WEBHOOK_SECRET; // dashboard → Webhooks
export async function POST(req) {
const rawBody = await req.text(); // MUST be the raw bytes
const header = req.headers.get('x-peptidepay-signature') ?? '';
const [ tPart, v1Part ] = header.split(',');
const t = tPart?.split('=')[1];
const v1 = v1Part?.split('=')[1];
if (!t || !v1) return new Response('bad sig', { status: 400 });
// Reject replays older than 5 minutes.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300)
return new Response('stale', { status: 400 });
const expected = crypto
.createHmac('sha256', SECRET)
.update(`${t}.${rawBody}`)
.digest('hex');
const ok =
v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'));
if (!ok) return new Response('invalid sig', { status: 401 });
const event = JSON.parse(rawBody);
// Idempotency: dedupe by event.session_id in your DB — retries re-fire
// the same event (with an incrementing "attempt" field) until you 2xx.
if (event.event === 'order.paid') {
await markOrderPaid(event.order_id, event.txid);
}
return new Response('ok');
}Política de retry
A gente reenvia respostas não-2xx (e timeouts > 5s) com backoff exponencial. Seis tentativas no total em ~42 horas:
- Tentativa 1 — imediatamente na confirmação.
- Tentativa 2 — +5 minutos.
- Tentativa 3 — +15 minutos.
- Tentativa 4 — +1 hora.
- Tentativa 5 — +4 horas.
- Tentativa 6 — +12 horas, depois +24 horas (última).
Depois de 6 tentativas falhadas, o evento vai pra dead-letter. Você pode re-consultar o estado atual a qualquer momento via .
Problemas comuns
- Tá dando 'invalid signature' em toda entrega
- Seu framework fez parse do body como JSON antes de você hashear. Lê os bytes RAW (Express: express.raw({type:'*/*'}); Next.js: req.text(); Laravel: request()->getContent(); Rails: request.raw_post). Nunca re-serializa antes de hashear.
- Whitelist de IP — de quais IPs vocês enviam?
- As entregas saem hoje do Vercel Edge (IPs dinâmicos). A gente não publica range estático. Se você PRECISA dar whitelist, usa o header de assinatura como gate de auth e aceita qualquer IP de origem — o HMAC é o check de identidade real.
- HTTPS obrigatório?
- Sim. A gente se recusa a fazer POST pra endpoints http:// (risco de confused deputy / replay em plaintext). A URL https grátis do ngrok funciona bem pra teste local.
- Meu endpoint é lento — posso estender o timeout de 5s?
- Não. Responde 2xx imediatamente, depois processa assíncrono (fila de jobs, setImmediate, goroutine). Handlers que bloqueiam muito sempre dão timeout.
SDKs
A API é pequena o bastante pra rolar perfeitamente — mas o SDK Node te dá types, retries automáticos e um helper que trata a verificação de assinatura pra você.
// npm install github:kinerette/peptide-pay-sdk
import { PeptidePay } from 'peptide-pay';
const pp = new PeptidePay(process.env.PEPTIDEPAY_API_KEY);
// Create a session
const session = await pp.checkout.create({
amount_cents: 5000,
currency: 'EUR',
customer_email: 'buyer@example.com',
success_url: 'https://mystore.com/success',
cancel_url: 'https://mystore.com/cart',
metadata: { order_id: '1234' },
});
// Retrieve a session
const latest = await pp.sessions.retrieve(session.id);
// Verify + parse a webhook (throws on invalid signature)
app.post('/webhooks/peptidepay', express.raw({ type: '*/*' }), (req, res) => {
const event = pp.webhooks.constructEvent(
req.body,
req.headers['x-peptidepay-signature'],
process.env.PEPTIDEPAY_WEBHOOK_SECRET,
);
// event.event === 'order.paid' (currently the only event delivered)
res.sendStatus(200);
});peptide-payTypes completos, helper de webhook, retries automáticos.
qualquer linguagemUm POST, um GET. Sem biblioteca.
Taxas
fixo — comissão completa do Peptide-Pay. Sem mensalidade, sem fee mensal, sem fee de chargeback. Taxas de on-ramp de cartão (~4.5% cobrados pelo processador de cartão upstream) são pass-through — o cliente paga, nunca toca no seu payout.
| Método de pagamento | Você paga | Cliente paga |
|---|---|---|
| Cartão / Apple Pay / Google Pay | 3% | ~4.5% (on-ramp, pass-through) |
| Cripto direto (USDC → USDC) | 3% | só gas (~US$ 0.01 na Polygon) |
Breakdown completo com exemplos resolvidos em /fees.
Testes
Toda conta merchant nova ganha grátis — a taxa cheia de 3% é estornada pra sua wallet em até 24h. Usa isso pra ensaiar o fluxo inteiro ponta a ponta (cartão real, USDC real, webhook real) antes de ir pro ar.
- Modo sandbox é automático: as primeiras 3 sessions pagas por merchant são marcadas como e qualificam pro refund automático.
- Cartão dev MoonPay: , qualquer validade futura, qualquer CVV, ZIP 10001.
- Teste de webhook local: expõe localhost com ngrok, cola a URL no campo por session.
Loop local completo (ngrok)
# 1. Expose your local webhook endpoint
ngrok http 3000
# 2. Copy the https://xxxx.ngrok-free.app URL and paste it into
# Dashboard → Webhooks → Endpoint URL, OR send it inline:
curl -X POST 'https://peptide-pay.com/api/v1/checkout/init' \
-H "Authorization: Bearer $PEPTIDEPAY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"amount_cents": 100,
"currency": "EUR",
"customer_email": "test+sandbox@yours.com",
"success_url": "https://yours.com/success",
"cancel_url": "https://yours.com/cart",
"webhook_url": "https://xxxx.ngrok-free.app/webhooks/peptidepay"
}'
# 3. Open the returned `url`, hit MoonPay's dev test card
# 4242 4242 4242 4242 (any future exp, any CVV).
# 4. Your local endpoint receives the signed POST within ~30s of payment.Erros & rate limits
Todos os erros têm o formato . Status codes são REST padrão.
400401403404429502Troubleshooting
- A session tá 'paid' no dashboard mas meu webhook nunca disparou
- Confere se webhook_url é alcançável via HTTPS público (curl fora da sua LAN). Se tá certo, faz poll em GET /sessions/{id} pra confirmar status — o dashboard /app mostra stats de entrega de webhook (taxa de sucesso, contagem). Seis tentativas em 42h antes da dead-letter; sempre pode re-sincronizar por polling.
- HMAC mismatch — assinatura sempre inválida
- 99% das vezes: você tá hasheando um body re-serializado em vez dos bytes raw. Frameworks fazem parse automático de JSON antes do seu handler rodar; você precisa do buffer raw. Next.js: req.text() antes de qualquer .json(). Express: app.use('/webhooks', express.raw({ type: '*/*' }), …). Rails: request.raw_post. Também confere se tá calculando `HMAC(whsec_secret, t + '.' + rawBody)` — NÃO só `HMAC(whsec_secret, rawBody)`. O prefixo do timestamp é obrigatório.
- MoonPay diz 'service unavailable in your country'
- MoonPay restringe ~20 países (Irã, Coreia do Norte, Cuba, lista completa no site deles). Provider default é 'gateway' — o picker inteligente cai automaticamente pra Revolut, Transak ou Banxa que cobrem geografias diferentes. Se você travou num provider específico com provider: 'moonpay', tira e deixa o router escolher.
- Minha wallet não recebeu USDC depois de um evento 'paid'
- Confere polygonscan.com/address/<sua-wallet> por transferências USDC (Polygon POS). Settlement cai 97% pra você e 3% pro Peptide-Pay - se você não vê os 97% entrando, pode ter colado a wallet errada na chamada de init. Re-confirma via GET /sessions/{id} - o campo txid aponta pra transferência on-chain real.
- Cliente foi cobrado duas vezes
- Não devia rolar. Cada session tem um addressIn de settlement; um segundo pagamento pro mesmo endereço vira uma session separada do nosso lado e só creditamos a primeira no seu pedido. Se rolar, screenshot dos dois txids do polygonscan + o session id e email pra hi@peptide-pay.com - a gente estorna o duplicado do nosso treasury.
- Tá dando 502 'Payment infrastructure temporarily unavailable'
- Nosso settlement upstream tá degradado (< 0.5% das requests). Retenta em 30s com o mesmo Idempotency-Key - nosso cache devolve a response original assim que a wallet gerar com sucesso. Acompanha /status pra incidentes ao vivo.
Pronto pra integrar?
A maioria dos merchants vai do zero até a primeira transação paga em menos de 30 minutos.