Riferimento sviluppatori

API Peptide-Pay

Una API REST per accettare carte e crypto, con settlement su un wallet USDC che controlli tu. Un POST crea un checkout. Un webhook ti dice che ha pagato. In produzione in meno di 30 minuti.

REST · JSONBearer authWebhook HMAC-SHA256Idempotency-KeyCORS abilitato

Inizia qui

Quickstart (5 min)

Tre step: crea una sessione, redirect il cliente, gestisci il webhook. L'esempio sotto è una route di checkout Node.js production-ready.

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

Questo è tutto l'happy path. Il cliente arriva su un checkout hosted su , sceglie carta o crypto, e il tuo webhook parte entro 30s dal pagamento.

Autenticazione

Due schemi, a seconda della modalità di integrazione:

SchemaComeQuando
Bearer tokenAuthorization: Bearer sk_live_…Server-side. Tiene il wallet privato.
Wallet nel body{ "wallet": "0x…", … }Siti statici / widget senza backend.
Attenzione
Le chiavi API portano l'intera identità del merchant — trattale come password. Non committarle mai, non mandarle mai al browser, ruotale da /app/api-keys se finiscono in chiaro.
Suggerimento
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.

Riferimento API

Base URL: . Tutti gli endpoint parlano JSON, restituiscono un singolo oggetto in caso di successo, e un oggetto su 4xx/5xx.

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

#Crea una sessione di checkout

Genera un URL di checkout hosted. Il cliente lo apre, paga con carta o crypto, Peptide-Pay fa settlement sul tuo wallet in USDC, parte il webhook.

Body della richiesta
CampoTipoObbligatorioDescrizione
amount_centsintegerobbligatorioImporto nell'unità minima della valuta (centesimi). Range 100 – 10 000 000.
currencystringobbligatorioCodice ISO 4217. Supportati: EUR, USD, GBP, CAD, AUD, CHF.
walletstringuno-diWallet USDC su Polygon (0x + 40 hex). Obbligatorio se non autenticato con Bearer key.
customer_emailstringopzionaleMostrato nella UI del checkout e passato all'on-ramp per riuso KYC.
success_urlurlopzionaleRedirect dopo pagamento riuscito. Solo http/https.
cancel_urlurlopzionaleRedirect se il cliente abbandona il checkout.
webhook_urlurlopzionaleTarget POST per gli eventi order.paid. Sovrascrive il default della dashboard.
providerstringopzionaleDefault 'gateway' (smart picker — consigliato). Oppure fissa un on-ramp specifico da GET /providers (es. moonpay, revolut, banxa, transak).
product_namestringopzionaleLabel mostrato sulla pagina di checkout (max 80 caratteri).
metadataobjectopzionaleFino a 10 coppie stringa chiave/valore, restituite nel webhook. Chiave riservata: order_id.
Risposta (200 OK)
CampoTipoObbligatorioDescrizione
idstringopzionaleSession id, inizia con cs_.
urlstringopzionaleURL di checkout hosted su cui mandare il cliente.
statusstringopzionaleSempre "pending" alla creazione.
amountintegeropzionaleEcho di amount_cents.
currencystringopzionaleEcho di currency.
providerstringopzionaleEcho di provider (default 'gateway').
expires_atstringopzionaleScadenza ISO 8601 (24h dalla creazione).
tracking_numberstringopzionaleIndirizzo di settlement Polygon — coincide con address_in nel payload del webhook, usabile con /track per il monitoring live.

Esempi

// 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);
Suggerimento
Passa per rendere i retry sicuri. Rigiochiamo la risposta cached per 24h sulla stessa chiave invece di creare una nuova sessione (previene doppi addebiti su reti instabili).
GEThttps://peptide-pay.com/api/v1/sessions/{id}

#Recupera una sessione (polling)

Usalo come fallback del webhook, o per idratare una pagina di successo dopo il redirect. Server-side ricontrolla il nostro layer di settlement a ogni chiamata — costa poco (<200ms), quindi il polling ogni 3-5s va bene.

Risposta
CampoTipoObbligatorioDescrizione
idstringopzionaleSession id.
statusstringopzionalepending | paid | expired | failed.
amountintegeropzionaleImporto originale in centesimi.
currencystringopzionaleValuta originale.
paid_atstring|nullopzionaleISO 8601 di quando il settlement on-chain è andato a buon fine.
paid_providerstring|nullopzionaleQuale provider ha effettivamente processato il pagamento (può differire da quello richiesto).
txidstring|nullopzionaleTxid di settlement su Polygon. Linka con polygonscan.com/tx/{txid}.
expires_atstringopzionaleScadenza 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

#Matrice provider live

Lista gli on-ramp che stanno accettando traffico, con importi minimi per provider. Cached 5 min all'edge — fai polling una volta al boot, non per richiesta.

Risposta
CampoTipoObbligatorioDescrizione
providers[].idstringopzionaleChiave provider (da passare come `provider` in /checkout/init).
providers[].provider_namestringopzionaleLabel human-readable per il dropdown.
providers[].statusstringopzionale'active' (sempre filtrato su active in questo endpoint).
providers[].minimum_currencystringopzionaleCodice ISO del minimo.
providers[].minimum_amountnumberopzionaleImporto minimo accettato dal provider (in unità di 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.

Webhook

Quando una sessione raggiunge uno stato terminale facciamo POST di un evento JSON firmato all{webhookUrl} che hai configurato (per-sessione o in dashboard). Parsa sempre il body {raw} della richiesta per verificare la firma — ri-serializzare il JSON riordina le chiavi e rompe lHMAC.

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
}

Tipi di evento

EventoQuando
order.paidSettlement on-chain confermato. + + garantiti presenti. Segna l'ordine come pagato.

Oggi viene consegnato solo — le sessioni expired e failed sono osservabili via (lo status diventa dopo il TTL di 24h; i failure terminali mostrano ). Potremmo aggiungere eventi push per quelli in una release futura.

Verifica firma

I merchant con un account signup ricevono un secret e ogni delivery porta un header nella forma . Calcola e confronta constant-time con . Rifiuta tutto ciò che è più vecchio di 5 minuti.

Suggerimento
I flow wallet-only (niente signup, niente ) consegnano il webhook non firmato — dovresti comunque validare che coincida con una sessione che hai creato. Per delivery firmate, registrati su /signup per avere il tuo 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');
}
Attenzione
Usa sempre il confronto constant-time del tuo linguaggio: (Node), (Python), (PHP), (Ruby). Un normale fa leakare l'HMAC un byte alla volta a un attacker di timing.

Policy di retry

Facciamo retry delle risposte non 2xx (e dei timeout > 5s) con backoff esponenziale. Sei tentativi totali in ~42 ore:

  • Tentativo 1 — subito alla conferma.
  • Tentativo 2 — +5 minuti.
  • Tentativo 3 — +15 minuti.
  • Tentativo 4 — +1 ora.
  • Tentativo 5 — +4 ore.
  • Tentativo 6 — +12 ore, poi +24 ore (finale).

Dopo 6 tentativi falliti l'evento va in dead-letter. Puoi ririchiedere lo stato attuale in qualsiasi momento via .

Suggerimento
Rendi il tuo handler idempotente. Dedup sul — il retry può rifare fire di un evento paid che hai già processato.

Problemi comuni

Vedo 'invalid signature' a ogni delivery
Il tuo framework ha parsato il body come JSON prima che tu lo hashassi. Leggi i byte RAW (Express: express.raw({type:'*/*'}); Next.js: req.text(); Laravel: request()->getContent(); Rails: request.raw_post). Non ri-serializzare mai prima dell'hash.
IP whitelist — da quali IP mandate?
Al momento le delivery partono da Vercel Edge (IP dinamici). Non pubblichiamo un range statico. Se DEVI whitelistare, usa l'header di firma come gate di auth e accetta qualsiasi IP sorgente — l'HMAC è il vero check di identità.
HTTPS obbligatorio?
Sì. Rifiutiamo di fare POST su endpoint http:// (rischio confusable deputy / replay in chiaro). L'URL https gratuito di ngrok funziona bene per il testing locale.
Il mio endpoint è lento — posso estendere il timeout di 5s?
No. Rispondi 2xx subito, poi processa asincrono (job queue, setImmediate, goroutine). Gli handler bloccanti lunghi finiscono sempre in timeout.

SDK

L'API è abbastanza piccola che va perfettamente bene — ma l'SDK Node ti dà i tipi, i retry automatici, e un helper che gestisce la verifica firma al posto tuo.

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

Tipi completi, helper webhook, retry automatici.

fetch() direttofunziona sempre
qualsiasi linguaggio

Un POST, un GET. Nessuna libreria necessaria.

Suggerimento
SDK Python, PHP, Ruby e Go sono in roadmap. Finché non escono, gli esempi raw // sopra sono il riferimento canonico — non li romperemo.

Commissioni

Flat — commissione totale Peptide-Pay. Niente abbonamento, niente canone mensile, niente commissioni di chargeback. Le commissioni on-ramp carta (~4.5% applicate dal card processor upstream) sono pass-through — le paga il cliente, non toccano mai il tuo payout.

Metodo di pagamentoPaghi tuPaga il cliente
Carta / Apple Pay / Google Pay3%~4.5% (on-ramp, pass-through)
Crypto diretto (USDC → USDC)3%solo gas (~$0.01 su Polygon)

Breakdown completo con esempi concreti su /fees.

Testing

Ogni nuovo account merchant riceve gratis — il 3% di commissione viene rimborsato sul tuo wallet entro 24h. Usali per provare il flusso end-to-end (carta vera, USDC veri, webhook veri) prima di andare live.

  • Modalità sandbox è automatica: le prime 3 sessioni pagate per merchant sono marcate e hanno diritto al rimborso automatico.
  • Dev card MoonPay: , qualsiasi scadenza futura, qualsiasi CVV, ZIP 10001.
  • Testing webhook locale: esponi localhost con ngrok, incolla l'URL nel campo della sessione.

Loop locale 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.

Errori e rate limit

Tutti gli errori hanno la forma . I codici di stato sono REST standard.

400
JSON invalido o campo mancante
Body malformato, amount non numerico, wallet non un indirizzo 0x, currency non supportata.
401
Chiave API invalida o revocata
Il Bearer token non risolve a un merchant. Ruotala su /app/api-keys.
403
Firma di callback errata
Interno — il nostro IPN di settlement ha colpito il ricevitore webhook senza la firma per-sessione corretta. Non è un errore merchant-facing in operazione normale.
404
Sessione non trovata
Id sbagliato, o la sessione è stata pruned (> 90g dopo stato terminale).
429
Rate limit superato
60 req/min/IP su init, 30 req/min/IP su select. Header Retry-After incluso. Contatta il support per tier più alti.
502
Upstream non disponibile
Rete di settlement temporaneamente degradata. Retry in 30s con lo stesso Idempotency-Key. Target SLA: 99.5%+.

Troubleshooting

La sessione è 'paid' in dashboard ma il mio webhook non è mai partito
Verifica che webhook_url sia raggiungibile in HTTPS pubblico (curl da fuori la tua LAN). Se è corretto, fai polling su GET /sessions/{id} per confermare lo status — la dashboard /app mostra le stats di delivery webhook (success rate, contatori). Sei tentativi in 42h prima del dead-letter; puoi sempre risincronizzare via polling.
Mismatch HMAC — la firma è sempre invalida
Nel 99% dei casi: stai hashando un body ri-serializzato invece dei byte raw. I framework parsano JSON in automatico prima che parta il tuo handler; ti serve il buffer raw. Next.js: req.text() prima di qualsiasi .json(). Express: app.use('/webhooks', express.raw({ type: '*/*' }), …). Rails: request.raw_post. Controlla anche che calcoli `HMAC(whsec_secret, t + '.' + rawBody)` — NON solo `HMAC(whsec_secret, rawBody)`. Il prefisso timestamp è obbligatorio.
MoonPay dice 'service unavailable in your country'
MoonPay restringe ~20 paesi (Iran, Corea del Nord, Cuba, lista completa sul loro sito). Il provider di default è 'gateway' — lo smart picker fa auto-fallback a Revolut, Transak o Banxa che coprono geografie diverse. Se hai fissato uno specifico provider con provider: 'moonpay', rimuovilo e lascia scegliere il router.
Il mio wallet non ha ricevuto USDC dopo un evento 'paid'
Controlla polygonscan.com/address/<tuo-wallet> per i trasferimenti USDC (Polygon POS). Il settlement arriva al 97% a te e al 3% a Peptide-Pay - se non vedi il 97% in entrata, potresti aver incollato il wallet sbagliato nella chiamata init. Riconferma via GET /sessions/{id} - il campo txid punta al trasferimento on-chain reale.
Il cliente è stato addebitato due volte
Non dovrebbe succedere. Ogni sessione ha un solo addressIn di settlement; un secondo pagamento sullo stesso indirizzo diventa una sessione separata da parte nostra e accreditiamo solo il primo al tuo ordine. Se succede, screenshot dei due txid polygonscan + session id via email a hi@peptide-pay.com - rimborsiamo il duplicato dalla nostra treasury.
Ricevo 502 'Payment infrastructure temporarily unavailable'
Il nostro upstream di settlement è degradato (< 0.5% delle richieste). Retry in 30s con lo stesso Idempotency-Key - la nostra cache restituisce la risposta originale appena il wallet viene generato correttamente. Tieni d'occhio /status per incidenti live.

Pronto a integrare?

La maggior parte dei merchant va da zero alla prima transazione pagata in meno di 30 minuti.