Referência do dev

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.

REST · JSONAuth BearerWebhooks HMAC-SHA256Idempotency-KeyCom CORS

Primeiros passos

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.

app/checkout/route.ts
// 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:

EsquemaComoQuando
Bearer tokenAuthorization: Bearer sk_live_…Server-side. Mantém a wallet privada.
Wallet no body{ "wallet": "0x…", … }Sites estáticos / widgets sem backend.
Atenção
API keys carregam a identidade completa da conta merchant — trata como senha. Nunca commita, nunca manda pro browser, rotaciona em /app/api-keys se vazar.
Dica
No sandbox mode. Signup returns both an 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.

POSThttps://peptide-pay.com/api/v1/checkout/init

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

Request body
CampoTipoObrigatórioDescrição
amount_centsintegerobrigatórioValor na menor unidade da moeda (centavos). Range 100 – 10 000 000.
currencystringobrigatórioCódigo ISO 4217. Suportadas: EUR, USD, GBP, CAD, AUD, CHF.
walletstringone-ofWallet USDC na Polygon (0x + 40 hex). Obrigatória se não estiver autenticado por Bearer key.
customer_emailstringopcionalExibido na UI do checkout e passado pra on-ramp pra reuso de KYC.
success_urlurlopcionalRedirect após pagamento com sucesso. Só http/https.
cancel_urlurlopcionalRedirect se o cliente abandonar o checkout.
webhook_urlurlopcionalAlvo do POST pros eventos order.paid. Sobrescreve o default do dashboard.
providerstringopcionalDefault 'gateway' (picker inteligente — recomendado). Ou trava num id específico de on-ramp de GET /providers (ex. moonpay, revolut, banxa, transak).
product_namestringopcionalLabel exibido na página de checkout (máx 80 chars).
metadataobjectopcionalAté 10 pares string key/value, devolvidos no webhook. Key reservada: order_id.
Response (200 OK)
CampoTipoObrigatórioDescrição
idstringopcionalSession id, começa com cs_.
urlstringopcionalURL de checkout hospedado pra redirecionar o cliente.
statusstringopcionalSempre "pending" na criação.
amountintegeropcionalEcho de amount_cents.
currencystringopcionalEcho de currency.
providerstringopcionalEcho de provider (default 'gateway').
expires_atstringopcionalExpiração ISO 8601 (24h da criação).
tracking_numberstringopcionalEndereç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);
Dica
Passa pra fazer retries seguros. A gente reproduz a response cacheada por 24h na mesma key em vez de gerar uma nova session (previne cobrança dupla em redes instáveis).
GEThttps://peptide-pay.com/api/v1/sessions/{id}

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

Response
CampoTipoObrigatórioDescrição
idstringopcionalSession id.
statusstringopcionalpending | paid | expired | failed.
amountintegeropcionalValor original em centavos.
currencystringopcionalMoeda original.
paid_atstring|nullopcionalISO 8601 de quando o settlement on-chain rolou.
paid_providerstring|nullopcionalQual provider processou de fato (pode diferir do requisitado).
txidstring|nullopcionalTxid de settlement Polygon. Link com polygonscan.com/tx/{txid}.
expires_atstringopcionalExpiraçã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');
}
GEThttps://peptide-pay.com/api/v1/providers

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

Response
CampoTipoObrigatórioDescrição
providers[].idstringopcionalChave do provider (passável como `provider` em /checkout/init).
providers[].provider_namestringopcionalLabel humano pro dropdown.
providers[].statusstringopcional'active' (sempre filtrado pra active nesse endpoint).
providers[].minimum_currencystringopcionalCódigo ISO do mínimo.
providers[].minimum_amountnumberopcionalMenor 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

EventoQuando
order.paidSettlement 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.

Dica
Fluxos wallet-only (sem cadastro, sem ) entregam o webhook sem assinatura — mesmo assim valida que bate com uma session que você criou. Pra entregas assinadas, cadastra em /signup pra pegar seu secret.
// 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');
}
Atenção
Sempre usa o compare de tempo constante da sua linguagem: (Node), (Python), (PHP), (Ruby). Um comum vaza o HMAC byte por byte pra um atacante de timing.

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 .

Dica
Faz seu handler ser idempotente. Dedupe em cima de — o retry pode re-disparar um evento paid que você já processou.

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
// 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);
});
Node / TypeScriptstable
peptide-pay

Types completos, helper de webhook, retries automáticos.

fetch() diretosempre funciona
qualquer linguagem

Um POST, um GET. Sem biblioteca.

Dica
SDKs de Python, PHP, Ruby e Go estão na roadmap. Até lá, os exemplos raw de // acima são a referência canônica — não vamos quebrar.

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 pagamentoVocê pagaCliente paga
Cartão / Apple Pay / Google Pay3%~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.

400
JSON inválido ou campo faltando
Body malformado, amount não-numérico, wallet não é endereço 0x, moeda não suportada.
401
API key inválida ou revogada
Bearer token não resolve pra nenhum merchant. Rotaciona em /app/api-keys.
403
Assinatura de callback errada
Interno — nosso IPN de settlement bateu no receiver do webhook sem a assinatura correta por session. Não é erro exposto ao merchant em operação normal.
404
Session não encontrada
Id errado, ou a session foi podada (> 90d depois de estado terminal).
429
Rate limit excedido
60 req/min/IP em init, 30 req/min/IP em select. Header Retry-After incluído. Fala com o suporte pra tiers mais altos.
502
Upstream indisponível
Rede de settlement degradada temporariamente. Retenta em 30s com o mesmo Idempotency-Key. Meta SLA: 99.5%+.

Troubleshooting

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.