Developer-referentie

Peptide-Pay API

Een REST API om kaarten en crypto te charten, met settlement naar een USDC-wallet die jij beheert. Eén POST maakt een checkout aan. Eén webhook vertelt je dat er is betaald. Ship in minder dan 30 minuten.

REST · JSONBearer authHMAC-SHA256 webhooksIdempotency-KeyCORS-enabled

Aan de slag

Quickstart (5 min)

Drie stappen: maak een sessie aan, redirect de klant, handle de webhook. Het voorbeeld hieronder is een production-ready Node.js checkout-route.

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

Dat is het hele happy path. De klant landt op een hosted checkout op , kiest kaart of crypto, en je webhook vuurt binnen 30s na betaling.

Authenticatie

Twee schema's, afhankelijk van de integratiemodus:

SchemaHoeWanneer
Bearer tokenAuthorization: Bearer sk_live_…Server-side. Houdt de wallet privé.
Wallet in body{ "wallet": "0x…", … }Statische sites / widgets zonder backend.
Let op
API-keys dragen de volledige identiteit van het merchant-account — behandel ze als wachtwoorden. Nooit committen, nooit naar de browser shippen, roteer ze vanuit /app/api-keys bij lekken.
Tip
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.

API-referentie

Base URL: . Alle endpoints spreken JSON, geven één object terug bij success, en een -object bij 4xx/5xx.

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

#Maak een checkout-sessie aan

Genereert een hosted checkout URL. De klant opent hem, betaalt met kaart of crypto, Peptide-Pay settelt naar je wallet in USDC, webhook vuurt.

Request body
VeldTypeVereistOmschrijving
amount_centsintegervereistBedrag in de kleinste valuta-eenheid (cents). Bereik 100 – 10 000 000.
currencystringvereistISO 4217-code. Ondersteund: EUR, USD, GBP, CAD, AUD, CHF.
walletstringone-ofUSDC-wallet op Polygon (0x + 40 hex). Vereist tenzij geauthenticeerd via Bearer-key.
customer_emailstringoptioneelGetoond in de checkout UI en doorgestuurd naar de on-ramp voor KYC-hergebruik.
success_urlurloptioneelRedirect na succesvolle betaling. Alleen http/https.
cancel_urlurloptioneelRedirect als de klant de checkout verlaat.
webhook_urlurloptioneelPOST-target voor order.paid events. Overschrijft de dashboard-default.
providerstringoptioneelDefault 'gateway' (smart picker — aanbevolen). Of pin een specifieke on-ramp id uit GET /providers (bv. moonpay, revolut, banxa, transak).
product_namestringoptioneelLabel getoond op de checkout-pagina (max 80 tekens).
metadataobjectoptioneelTot 10 string key/value pairs, echoed terug in de webhook. Gereserveerde key: order_id.
Response (200 OK)
VeldTypeVereistOmschrijving
idstringoptioneelSessie-id, begint met cs_.
urlstringoptioneelHosted checkout URL om de klant naar te redirecten.
statusstringoptioneelAltijd "pending" bij creatie.
amountintegeroptioneelEcho van amount_cents.
currencystringoptioneelEcho van currency.
providerstringoptioneelEcho van provider (default 'gateway').
expires_atstringoptioneelISO 8601 expiry (24u vanaf creatie).
tracking_numberstringoptioneelPolygon settlement-adres — matcht address_in in de webhook payload, bruikbaar met /track voor live monitoring.

Voorbeelden

// 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);
Tip
Pass om retries veilig te maken. We replayen de gecachte response voor 24u op dezelfde key in plaats van een nieuwe sessie aan te maken (voorkomt dubbele afschrijvingen op flaky netwerken).
GEThttps://peptide-pay.com/api/v1/sessions/{id}

#Een sessie ophalen (polling)

Gebruik dit als webhook-fallback, of om een success-pagina te hydrateren na redirect. Server-side re-checkt onze settlement-laag bij elke call — goedkoop (<200ms), dus elke 3-5s pollen is prima.

Response
VeldTypeVereistOmschrijving
idstringoptioneelSessie-id.
statusstringoptioneelpending | paid | expired | failed.
amountintegeroptioneelOorspronkelijk bedrag in cents.
currencystringoptioneelOorspronkelijke valuta.
paid_atstring|nulloptioneelISO 8601 wanneer de on-chain settlement voltooide.
paid_providerstring|nulloptioneelWelke provider daadwerkelijk de betaling heeft verwerkt (kan verschillen van de gevraagde).
txidstring|nulloptioneelPolygon settlement txid. Link met polygonscan.com/tx/{txid}.
expires_atstringoptioneelISO 8601 expiry.
// 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

#Live provider-matrix

Lijst van de on-ramps die momenteel traffic accepteren, met per-provider minimumbedragen. 5 min gecached aan de edge — poll één keer bij app-boot, niet per request.

Response
VeldTypeVereistOmschrijving
providers[].idstringoptioneelProvider-key (door te geven als `provider` in /checkout/init).
providers[].provider_namestringoptioneelMenselijk label voor de dropdown.
providers[].statusstringoptioneel'active' (altijd gefilterd op active bij dit endpoint).
providers[].minimum_currencystringoptioneelISO-code van het minimum.
providers[].minimum_amountnumberoptioneelLaagste bedrag dat de provider accepteert (in minimum_currency-eenheden).
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

Wanneer een sessie een terminale status bereikt, POSTen we een getekend JSON-event naar de die je hebt geconfigureerd (per sessie of in het dashboard). Parse altijd de request body voor signature-verificatie — JSON opnieuw serialiseren herordent keys en breekt de 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
}

Event types

EventWanneer
order.paidOn-chain settlement bevestigd. + + zijn gegarandeerd aanwezig. Markeer de order als betaald.

Alleen wordt vandaag geleverd — verlopen en mislukte sessies zijn zichtbaar via (status gaat naar na de 24u TTL; terminale failures tonen ). We voegen mogelijk push-events toe voor die in een toekomstige release.

Signature-verificatie

Merchants met een signup-account krijgen een secret en elke delivery draagt een header in de vorm . Bereken en doe een constant-time compare met . Weiger alles ouder dan 5 minuten.

Tip
Wallet-only flows (geen signup, geen ) leveren de webhook unsigned — je moet nog steeds valideren dat de matcht met een sessie die jij hebt aangemaakt. Voor signed deliveries, meld je aan op /signup om je secret te krijgen.
// 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');
}
Let op
Gebruik altijd de constant-time compare voor je taal: (Node), (Python), (PHP), (Ruby). Een gewone lekt de HMAC byte voor byte naar een timing-aanvaller.

Retry policy

We retryen non-2xx responses (en timeouts > 5s) op een exponentieel backoff. Zes totale pogingen over ~42 uur:

  • Poging 1 — meteen bij bevestiging.
  • Poging 2 — +5 minuten.
  • Poging 3 — +15 minuten.
  • Poging 4 — +1 uur.
  • Poging 5 — +4 uur.
  • Poging 6 — +12 uur, dan +24 uur (finaal).

Na 6 mislukte pogingen wordt het event dead-lettered. Je kunt de huidige state op elk moment opnieuw opvragen via .

Tip
Maak je handler idempotent. Dedupe op — de retry kan een paid event opnieuw vuren dat je al verwerkt hebt.

Veelvoorkomende issues

Ik zie 'invalid signature' op elke delivery
Je framework parsete de body als JSON voordat je hashte. Lees de RAW bytes (Express: express.raw({type:'*/*'}); Next.js: req.text(); Laravel: request()->getContent(); Rails: request.raw_post). Nooit opnieuw serialiseren voor het hashen.
IP-whitelist — vanaf welke IPs versturen jullie?
Deliveries komen momenteel van Vercel Edge (dynamische IPs). We publiceren geen statische range. Als je MOET whitelisten, gebruik de signature-header als je auth-gate en accepteer elke source IP — de HMAC is de echte identiteitscheck.
HTTPS vereist?
Ja. We weigeren te POSTen naar http:// endpoints (confusable deputy / plaintext replay-risico). ngrok's gratis https-URL werkt prima voor lokaal testen.
Mijn endpoint is traag — kan ik de 5s timeout verlengen?
Nee. Reageer meteen 2xx, verwerk daarna asynchroon (job queue, setImmediate, goroutine). Lang blokkerende handlers timen altijd uit.

SDKs

De API is klein genoeg dat perfect werkt — maar de Node SDK geeft je types, automatische retries, en een -helper die signature-verificatie voor je regelt.

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

Volledige types, webhook-helper, automatische retries.

Directe fetch()werkt altijd
elke taal

Eén POST, één GET. Geen library nodig.

Tip
Python-, PHP-, Ruby- en Go-SDKs staan op de roadmap. Tot ze shippen zijn de raw //-voorbeelden hierboven de canonieke referentie — we zullen ze niet breken.

Tarieven

Flat — de volledige commissie van Peptide-Pay. Geen abonnement, geen maandelijkse fee, geen chargeback fees. Card on-ramp fees (~4.5% door de upstream card processor aangerekend) zijn pass-through — de klant betaalt ze, ze raken nooit je payout.

BetaalmethodeJij betaaltKlant betaalt
Kaart / Apple Pay / Google Pay3%~4.5% (on-ramp, pass-through)
Crypto direct (USDC → USDC)3%alleen gas (~$0.01 op Polygon)

Volledige breakdown met uitgewerkte voorbeelden op /fees.

Testen

Elke nieuwe merchant-account krijgt gratis — de volledige 3% fee wordt binnen 24u teruggestort in je wallet. Gebruik deze om de volledige flow end-to-end te rehearsen (echte kaart, echte USDC, echte webhook) voordat je live gaat.

  • Sandbox-modus is automatisch: de eerste 3 betaalde sessies per merchant worden gemarkeerd als en komen in aanmerking voor auto-refund.
  • MoonPay dev card: , elke toekomstige expiry, elke CVV, ZIP 10001.
  • Lokaal webhook testen: expose localhost met ngrok, plak de URL in het -veld per sessie.

Volledige lokale loop (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.

Errors & rate limits

Alle errors delen de shape . Statuscodes zijn standaard REST.

400
Ongeldige JSON of ontbrekend veld
Body misvormd, amount niet numeriek, wallet geen 0x-adres, currency niet ondersteund.
401
Ongeldige of ingetrokken API-key
Bearer-token resolved niet naar een merchant. Roteer hem op /app/api-keys.
403
Foute callback-signature
Intern — onze settlement IPN hit de webhook-receiver zonder de correcte per-sessie signature. Geen merchant-facing fout in normale werking.
404
Sessie niet gevonden
Foute id, of de sessie is gepruned (> 90d na terminale state).
429
Rate limit overschreden
60 req/min/IP op init, 30 req/min/IP op select. Retry-After header inbegrepen. Neem contact op met support voor hogere tiers.
502
Upstream niet beschikbaar
Settlement-netwerk tijdelijk gedegradeerd. Retry in 30s met dezelfde Idempotency-Key. SLA-target: 99.5%+.

Troubleshooting

Sessie is 'paid' in het dashboard maar mijn webhook is nooit afgevuurd
Check of webhook_url bereikbaar is over publieke HTTPS (curl hem van buiten je LAN). Als het klopt, poll GET /sessions/{id} om de status te bevestigen — het dashboard /app toont webhook delivery stats (success rate, counts). Zes pogingen over 42u voor dead-letter; je kunt altijd resyncen via polling.
HMAC mismatch — signature is altijd invalid
99% van de tijd: je hasht een opnieuw geserialiseerde body in plaats van de raw bytes. Frameworks parsen JSON automatisch voordat je handler draait; je hebt de raw buffer nodig. Next.js: req.text() voor elke .json(). Express: app.use('/webhooks', express.raw({ type: '*/*' }), …). Rails: request.raw_post. Check ook dat je `HMAC(whsec_secret, t + '.' + rawBody)` berekent — NIET gewoon `HMAC(whsec_secret, rawBody)`. De timestamp-prefix is vereist.
MoonPay zegt 'service unavailable in your country'
MoonPay beperkt ~20 landen (Iran, Noord-Korea, Cuba, volledige lijst op hun site). Default provider is 'gateway' — de smart picker valt automatisch terug op Revolut, Transak of Banxa, die andere geografieën dekken. Als je een specifieke provider hebt gepind met provider: 'moonpay', laat hem weg en laat de router kiezen.
Mijn wallet heeft geen USDC ontvangen na een 'paid' event
Check polygonscan.com/address/<your-wallet> voor USDC (Polygon POS) transfers. Settlement landt 97% bij jou en 3% bij Peptide-Pay — als je de 97% inbound niet ziet, heb je mogelijk de verkeerde wallet in de init-call geplakt. Herbevestig via GET /sessions/{id} — het txid-veld wijst naar de echte on-chain transfer.
Klant werd twee keer afgeschreven
Zou niet moeten gebeuren. Elke sessie heeft één settlement addressIn; een tweede betaling naar hetzelfde adres wordt aan onze kant een aparte sessie en we crediteren alleen de eerste aan je order. Als het gebeurt, screenshot de twee polygonscan-txids + de sessie-id en mail hi@peptide-pay.com — we refunden het duplicaat vanuit onze treasury.
Ik krijg 502 'Payment infrastructure temporarily unavailable'
Onze settlement-upstream is gedegradeerd (< 0.5% van requests). Retry in 30s met dezelfde Idempotency-Key — onze cache geeft de originele response terug zodra de wallet succesvol wordt gegenereerd. Volg /status voor live incidents.

Klaar om te integreren?

De meeste merchants gaan van nul naar hun eerste betaalde transactie in minder dan 30 minuten.