Peptide-Pay API
Eine REST-API, um Karten und Krypto zu charguen und auf eine USDC-Wallet zu settlen, die du kontrollierst. Ein POST erstellt einen Checkout. Ein Webhook sagt dir, dass bezahlt wurde. Live in unter 30 Minuten.
Getting Started
Peptide-Pay unterstützt vier Integrationsmodi. Wähl den, der zu deinen Anforderungen passt; die darunterliegende API ist dieselbe.
Drop-in Button, kein Signup. Gib deine USDC-Wallet-Adresse im Request Body mit. Null Backend-State; public, sichtbar in DevTools.
Server-to-Server. Versteck deine Wallet hinter Bearer sk_live_. Branding, Webhooks, Mass Payouts im Dashboard konfigurieren.
Fertiges Plugin. ZIP hochladen, API-Key + Webhook-Secret einfügen, Bestellungen werden bei Zahlung automatisch abgeschlossen. HPOS-ready, WC 7.0+.
Custom App + Manuelle Zahlungsmethode. ~30 Min Installation auf einem bestehenden Store. Bestellungen werden via Shopify Admin API als bezahlt markiert.
Quickstart (5 Min.)
Drei Schritte: Session erstellen, Kunden redirecten, Webhook behandeln. Das Sample unten ist eine produktionsreife 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);Das ist der ganze Happy Path. Der Kunde landet auf einem gehosteten Checkout auf , wählt Karte oder Krypto, und dein Webhook feuert innerhalb von 30s nach Zahlung.
Authentifizierung
Zwei Schemata, je nach Integrationsmodus:
| Schema | Wie | Wann |
|---|---|---|
| Bearer Token | Authorization: Bearer sk_live_… | Serverseitig. Hält die Wallet privat. |
| Wallet im Body | { "wallet": "0x…", … } | Statische Sites / Widgets ohne 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-Referenz
Base URL: . Alle Endpunkte sprechen JSON, liefern bei Erfolg ein einzelnes Objekt zurück und ein -Objekt bei 4xx/5xx.
#Eine Checkout-Session erstellen
Erzeugt eine gehostete Checkout-URL. Der Kunde öffnet sie, zahlt mit Karte oder Krypto, Peptide-Pay settlet auf deine Wallet in USDC, Webhook feuert.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| amount_cents | integer | Pflicht | Betrag in der kleinsten Währungseinheit (Cent). Bereich 100 – 10 000 000. |
| currency | string | Pflicht | ISO-4217-Code. Unterstützt: EUR, USD, GBP, CAD, AUD, CHF. |
| wallet | string | one-of | USDC-Wallet auf Polygon (0x + 40 Hex). Pflicht, außer bei Auth via Bearer-Key. |
| customer_email | string | optional | Wird in der Checkout-UI angezeigt und an den On-Ramp für KYC-Wiederverwendung weitergeleitet. |
| success_url | url | optional | Redirect nach erfolgreicher Zahlung. Nur http/https. |
| cancel_url | url | optional | Redirect, falls der Kunde den Checkout abbricht. |
| webhook_url | url | optional | POST-Ziel für order.paid-Events. Überschreibt den Dashboard-Default. |
| provider | string | optional | Default 'gateway' (Smart Picker — empfohlen). Oder eine spezifische On-Ramp-ID aus GET /providers (z.B. moonpay, revolut, banxa, transak) pinnen. |
| product_name | string | optional | Label auf der Checkout-Seite (max. 80 Zeichen). |
| metadata | object | optional | Bis zu 10 String-Key/Value-Paare, die im Webhook zurückgespiegelt werden. Reservierter Key: order_id. |
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| id | string | optional | Session-ID, beginnt mit cs_. |
| url | string | optional | Gehostete Checkout-URL, auf die der Kunde redirectet wird. |
| status | string | optional | Immer "pending" bei Erstellung. |
| amount | integer | optional | Echo von amount_cents. |
| currency | string | optional | Echo der Währung. |
| provider | string | optional | Echo des Providers (Default 'gateway'). |
| expires_at | string | optional | ISO-8601-Ablaufdatum (24h ab Erstellung). |
| tracking_number | string | optional | Polygon-Settlement-Adresse — entspricht address_in im Webhook-Payload, nutzbar mit /track für Live-Monitoring. |
Beispiele
// 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);#Eine Session abrufen (Polling)
Nutze das als Webhook-Fallback oder um eine Success-Page nach Redirect zu hydraten. Serverseitig re-checken wir unseren Settlement-Layer bei jedem Call — günstig (<200ms), Polling alle 3-5s ist also ok.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| id | string | optional | Session-ID. |
| status | string | optional | pending | paid | expired | failed. |
| amount | integer | optional | Originalbetrag in Cent. |
| currency | string | optional | Originalwährung. |
| paid_at | string|null | optional | ISO 8601, wann das On-Chain-Settlement abgeschlossen wurde. |
| paid_provider | string|null | optional | Welcher Provider die Zahlung tatsächlich abgewickelt hat (kann vom angefragten abweichen). |
| txid | string|null | optional | Polygon-Settlement-txid. Verlinken mit polygonscan.com/tx/{txid}. |
| expires_at | string | optional | ISO 8601 Ablaufdatum. |
// 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
Listet die aktuell Traffic akzeptierenden On-Ramps mit per-Provider-Mindestbeträgen. 5 Min. am Edge gecacht — beim App-Boot einmal pollen, nicht pro Request.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| providers[].id | string | optional | Provider-Key (als `provider` in /checkout/init übergebbar). |
| providers[].provider_name | string | optional | Menschliches Label für das Dropdown. |
| providers[].status | string | optional | 'active' (an diesem Endpunkt immer auf active gefiltert). |
| providers[].minimum_currency | string | optional | ISO-Code des Mindestbetrags. |
| providers[].minimum_amount | number | optional | Niedrigster Betrag, den der Provider akzeptiert (in minimum_currency-Einheiten). |
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
Wenn eine Session einen terminalen Zustand erreicht, POSTen wir ein signiertes JSON-Event an die , die du konfiguriert hast (per Session oder im Dashboard). Immer den Request-Body für die Signaturverifizierung parsen — JSON neu serialisieren reordert Keys und bricht den 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-Typen
| Event | Wann |
|---|---|
| order.paid | On-Chain-Settlement bestätigt. + + garantiert vorhanden. Bestellung als bezahlt markieren. |
Heute wird nur ausgeliefert — abgelaufene und fehlgeschlagene Sessions sind via beobachtbar (Status geht nach 24h TTL auf ; terminale Fehler zeigen ). Wir könnten in einem zukünftigen Release Push-Events dafür ergänzen.
Signaturverifizierung
Merchants mit Signup-Account erhalten ein -Secret und jede Delivery trägt einen -Header der Form . Berechne und constant-time-vergleiche mit . Alles älter als 5 Minuten ablehnen.
// 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
Wir retryen non-2xx-Responses (und Timeouts > 5s) mit exponentiellem Backoff. Sechs Versuche insgesamt über ~42 Stunden:
- Versuch 1 — sofort bei Bestätigung.
- Versuch 2 — +5 Minuten.
- Versuch 3 — +15 Minuten.
- Versuch 4 — +1 Stunde.
- Versuch 5 — +4 Stunden.
- Versuch 6 — +12 Stunden, dann +24 Stunden (final).
Nach 6 fehlgeschlagenen Versuchen wird das Event dead-lettered. Du kannst den aktuellen State jederzeit via abfragen.
Häufige Probleme
- Ich sehe 'invalid signature' bei jeder Delivery
- Dein Framework hat den Body als JSON geparst, bevor du ihn gehasht hast. Lies die ROHEN Bytes (Express: express.raw({type:'*/*'}); Next.js: req.text(); Laravel: request()->getContent(); Rails: request.raw_post). Nie vor dem Hashen neu serialisieren.
- IP-Whitelist — von welchen IPs sendet ihr?
- Deliveries kommen aktuell von Vercel Edge (dynamische IPs). Wir veröffentlichen keinen statischen Bereich. Wenn du whitelisten MUSST, nutz den Signature-Header als Auth-Gate und akzeptiere jede Source-IP — der HMAC ist der echte Identitäts-Check.
- HTTPS erforderlich?
- Ja. Wir weigern uns, an http://-Endpunkte zu POSTen (Confusable-Deputy-/Plaintext-Replay-Risiko). Die gratis HTTPS-URL von ngrok funktioniert fürs lokale Testen wunderbar.
- Mein Endpunkt ist langsam — kann ich das 5s-Timeout verlängern?
- Nein. Antworte sofort 2xx, dann asynchron verarbeiten (Job-Queue, setImmediate, Goroutine). Lange blockende Handler laufen immer in Timeouts.
SDKs
Die API ist klein genug, dass absolut in Ordnung ist — aber das Node-SDK gibt dir Types, automatische Retries und einen -Helper, der die Signaturverifizierung für dich übernimmt.
// 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-payVolle Types, Webhook-Helper, automatische Retries.
beliebige SpracheEin POST, ein GET. Keine Lib nötig.
Gebühren
Flat — Peptide-Pays volle Kommission. Kein Abo, keine Monatsgebühr, keine Chargeback-Gebühren. On-Ramp-Gebühren für Karten (~4.5%, vom Upstream-Kartenprocessor erhoben) sind Pass-Through — der Kunde zahlt sie, sie berühren nie deinen Payout.
| Zahlungsmethode | Du zahlst | Kunde zahlt |
|---|---|---|
| Karte / Apple Pay / Google Pay | 3% | ~4.5% (On-Ramp, Pass-Through) |
| Krypto direkt (USDC → USDC) | 3% | nur Gas (~$0.01 auf Polygon) |
Volle Aufschlüsselung mit Rechenbeispielen unter /fees.
Testing
Jeder neue Merchant-Account bekommt gratis — die volle 3%-Gebühr wird innerhalb von 24h auf deine Wallet zurückerstattet. Nutz sie, um den vollen Flow end-to-end (echte Karte, echtes USDC, echter Webhook) zu proben, bevor du live gehst.
- Sandbox-Modus ist automatisch: die ersten 3 bezahlten Sessions pro Merchant werden als markiert und qualifizieren sich für Auto-Refund.
- MoonPay-Dev-Karte: , beliebiges zukünftiges Ablaufdatum, beliebige CVV, ZIP 10001.
- Lokales Webhook-Testing: localhost mit ngrok exponieren, URL pro Session ins -Feld einfügen.
Voller lokaler 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.Fehler & Rate Limits
Alle Fehler teilen die Form . Statuscodes sind Standard-REST.
400401403404429502Troubleshooting
- Session ist im Dashboard 'paid', aber mein Webhook hat nie gefeuert
- Prüf, dass webhook_url über öffentliches HTTPS erreichbar ist (curl es von außerhalb deines LANs). Wenn das stimmt, poll GET /sessions/{id} zur Status-Bestätigung — das Dashboard /app zeigt Webhook-Delivery-Stats (Success Rate, Counts). Sechs Versuche über 42h vor Dead-Letter; du kannst immer per Polling re-syncen.
- HMAC-Mismatch — Signatur ist immer invalid
- In 99% der Fälle: du hashst einen neu-serialisierten Body statt der rohen Bytes. Frameworks parsen JSON auto vor deinem Handler; du brauchst den rohen Buffer. Next.js: req.text() vor jedem .json(). Express: app.use('/webhooks', express.raw({ type: '*/*' }), …). Rails: request.raw_post. Check auch, dass du `HMAC(whsec_secret, t + '.' + rawBody)` berechnest — NICHT nur `HMAC(whsec_secret, rawBody)`. Das Timestamp-Prefix ist Pflicht.
- MoonPay sagt 'service unavailable in your country'
- MoonPay sperrt ~20 Länder (Iran, Nordkorea, Kuba, volle Liste auf deren Site). Default-Provider ist 'gateway' — der Smart Picker fällt automatisch auf Revolut, Transak oder Banxa zurück, die andere Regionen abdecken. Wenn du einen spezifischen Provider mit provider: 'moonpay' gepinnt hast, lass es weg und lass den Router wählen.
- Meine Wallet hat nach einem 'paid'-Event kein USDC bekommen
- Prüf polygonscan.com/address/<deine-wallet> auf USDC-(Polygon POS)-Transfers. Settlement landet zu 97% bei dir und zu 3% bei Peptide-Pay — wenn du die 97% nicht eingehend siehst, hast du evtl. die falsche Wallet in den Init-Call eingefügt. Re-check via GET /sessions/{id} — das txid-Feld zeigt auf den echten On-Chain-Transfer.
- Kunde wurde doppelt belastet
- Sollte nicht passieren. Jede Session hat eine Settlement-addressIn; eine zweite Zahlung an dieselbe Adresse wird bei uns zu einer separaten Session und wir creditieren nur die erste deiner Bestellung zu. Falls doch, Screenshot der zwei Polygonscan-txids + Session-ID an hi@peptide-pay.com — wir refunden das Duplikat aus unserer Treasury.
- Ich bekomme 502 'Payment infrastructure temporarily unavailable'
- Unser Settlement-Upstream ist degraded (< 0.5% der Requests). Retry in 30s mit dem gleichen Idempotency-Key — unser Cache gibt die Original-Response zurück, sobald die Wallet erfolgreich mintet. Track /status für Live-Incidents.
Bereit zu integrieren?
Die meisten Merchants gehen in unter 30 Minuten von Null zur ersten bezahlten Transaktion.