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.
Aan de slag
Peptide-Pay ondersteunt vier integratiemodi. Kies degene die bij jouw beperkingen past; de onderliggende API is dezelfde.
Drop-in knop, geen signup. Pass je USDC wallet-adres in de request body. Nul backend state; publiek, zichtbaar in DevTools.
Server-to-server. Verberg je wallet achter Bearer sk_live_. Configureer branding, webhooks, mass payouts vanuit het dashboard.
Kant-en-klare plugin. Upload de ZIP, plak je API-key + webhook secret, orders worden automatisch afgerond bij betaling. HPOS-ready, WC 7.0+.
Custom App + Handmatige Betaalmethode. ~30 min installatie op een bestaande winkel. We markeren bestellingen als betaald via de Shopify Admin API.
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.
// 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:
| Schema | Hoe | Wanneer |
|---|---|---|
| Bearer token | Authorization: Bearer sk_live_… | Server-side. Houdt de wallet privé. |
| Wallet in body | { "wallet": "0x…", … } | Statische sites / widgets zonder 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.API-referentie
Base URL: . Alle endpoints spreken JSON, geven één object terug bij success, en een -object bij 4xx/5xx.
#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.
| Veld | Type | Vereist | Omschrijving |
|---|---|---|---|
| amount_cents | integer | vereist | Bedrag in de kleinste valuta-eenheid (cents). Bereik 100 – 10 000 000. |
| currency | string | vereist | ISO 4217-code. Ondersteund: EUR, USD, GBP, CAD, AUD, CHF. |
| wallet | string | one-of | USDC-wallet op Polygon (0x + 40 hex). Vereist tenzij geauthenticeerd via Bearer-key. |
| customer_email | string | optioneel | Getoond in de checkout UI en doorgestuurd naar de on-ramp voor KYC-hergebruik. |
| success_url | url | optioneel | Redirect na succesvolle betaling. Alleen http/https. |
| cancel_url | url | optioneel | Redirect als de klant de checkout verlaat. |
| webhook_url | url | optioneel | POST-target voor order.paid events. Overschrijft de dashboard-default. |
| provider | string | optioneel | Default 'gateway' (smart picker — aanbevolen). Of pin een specifieke on-ramp id uit GET /providers (bv. moonpay, revolut, banxa, transak). |
| product_name | string | optioneel | Label getoond op de checkout-pagina (max 80 tekens). |
| metadata | object | optioneel | Tot 10 string key/value pairs, echoed terug in de webhook. Gereserveerde key: order_id. |
| Veld | Type | Vereist | Omschrijving |
|---|---|---|---|
| id | string | optioneel | Sessie-id, begint met cs_. |
| url | string | optioneel | Hosted checkout URL om de klant naar te redirecten. |
| status | string | optioneel | Altijd "pending" bij creatie. |
| amount | integer | optioneel | Echo van amount_cents. |
| currency | string | optioneel | Echo van currency. |
| provider | string | optioneel | Echo van provider (default 'gateway'). |
| expires_at | string | optioneel | ISO 8601 expiry (24u vanaf creatie). |
| tracking_number | string | optioneel | Polygon 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);#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.
| Veld | Type | Vereist | Omschrijving |
|---|---|---|---|
| id | string | optioneel | Sessie-id. |
| status | string | optioneel | pending | paid | expired | failed. |
| amount | integer | optioneel | Oorspronkelijk bedrag in cents. |
| currency | string | optioneel | Oorspronkelijke valuta. |
| paid_at | string|null | optioneel | ISO 8601 wanneer de on-chain settlement voltooide. |
| paid_provider | string|null | optioneel | Welke provider daadwerkelijk de betaling heeft verwerkt (kan verschillen van de gevraagde). |
| txid | string|null | optioneel | Polygon settlement txid. Link met polygonscan.com/tx/{txid}. |
| expires_at | string | optioneel | ISO 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');
}#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.
| Veld | Type | Vereist | Omschrijving |
|---|---|---|---|
| providers[].id | string | optioneel | Provider-key (door te geven als `provider` in /checkout/init). |
| providers[].provider_name | string | optioneel | Menselijk label voor de dropdown. |
| providers[].status | string | optioneel | 'active' (altijd gefilterd op active bij dit endpoint). |
| providers[].minimum_currency | string | optioneel | ISO-code van het minimum. |
| providers[].minimum_amount | number | optioneel | Laagste 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
| Event | Wanneer |
|---|---|
| order.paid | On-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.
// 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');
}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 .
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
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-payVolledige types, webhook-helper, automatische retries.
elke taalEén POST, één GET. Geen library nodig.
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.
| Betaalmethode | Jij betaalt | Klant betaalt |
|---|---|---|
| Kaart / Apple Pay / Google Pay | 3% | ~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.
400401403404429502Troubleshooting
- 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.