PeptidePay 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
PeptidePay 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. PeptidePaysettles 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, PeptidePay 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 | Deine EVM-Wallet-Adresse (0x + 40 Hex), die Ethereum und Polygon akzeptiert. Pflicht, außer bei Authentifizierung per 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 | Settlement-Adresse (0x…). 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 | Settlement-txid. Nachschlagen auf etherscan.io/tx/{txid} (die meisten Zahlungen) oder 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 peptidepay = new PeptidePay(process.env.PEPTIDEPAY_API_KEY);
// Create a session
const session = await peptidepay.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 peptidepay.sessions.retrieve(session.id);
// Verify + parse a webhook (throws on invalid signature)
app.post('/webhooks/peptidepay', express.raw({ type: '*/*' }), (req, res) => {
const event = peptidepay.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);
});PeptidePayVolle Types, Webhook-Helper, automatische Retries.
beliebige SpracheEin POST, ein GET. Keine Lib nötig.
Gebühren
Flat — PeptidePays 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 Netzwerk-Gas |
Volle Aufschlüsselung mit Rechenbeispielen unter /fees.
Testing
PeptidePay hat kein Sandbox-Netzwerk — jedes Settlement ist ein echter On-Chain-Transfer. Der risikoärmste Test ist , das du dir selbst zurückerstattest. Um deinen Webhook end-to-end ohne Zahlung auszuprobieren, nutz den Test-Simulator unten.
- Webhook-Simulator: POST an /api/v1/test/fire-webhook mit einem sk_test_-Key, um ein signiertes order.paid an deinen Endpunkt zu feuern — keine Zahlung, kein Refund nötig.
- 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)
# Test your webhook receiver WITHOUT a real on-ramp payment.
# Do NOT test by paying a tiny amount: anything below an on-ramp's
# minimum (~$20) hits the gateway's "below minimum" error + KYC — that's
# the live on-ramp, not your webhook. Use the signed simulator instead.
# 1. Expose your local webhook endpoint
ngrok http 3000
# 2. Create a TEST session with your sk_test_ key. Amount can be anything
# (the simulator ignores on-ramp minimums). Note the "id" it returns.
curl -X POST 'https://peptide-pay.com/api/v1/checkout/init' \
-H "Authorization: Bearer $PEPTIDEPAY_TEST_KEY" \
-H 'Content-Type: application/json' \
-d '{
"amount_cents": 5000,
"currency": "EUR",
"customer_email": "test+sandbox@yours.com",
"webhook_url": "https://xxxx.ngrok-free.app/webhooks/peptidepay"
}'
# 3. Fire a SIGNED order.paid straight at your endpoint — no on-ramp, no
# minimum, no KYC. (webhook_url_override also accepts localhost / ngrok
# / loca.lt / trycloudflare.com if you'd rather not set it on the session.)
curl -X POST 'https://peptide-pay.com/api/v1/test/fire-webhook' \
-H "Authorization: Bearer $PEPTIDEPAY_TEST_KEY" \
-H 'Content-Type: application/json' \
-d '{ "session_id": "PASTE_id_FROM_STEP_2" }'
# 4. The response echoes your receiver's HTTP status + the exact signature
# header we sent, so you can line it up against your HMAC verification.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 nichts erhalten
- Prüfe zuerst etherscan.io/address/<your-wallet> (ETH und ERC-20-USDC, dort landen die meisten Zahlungen), dann polygonscan.com/address/<your-wallet>. Es ist dieselbe 0x-Adresse in beiden Netzwerken. 97 % gehen an dich, 3 % an PeptidePay. Siehst du in keinem Netzwerk einen Eingang, hast du vielleicht die falsche Wallet im init-Aufruf angegeben. Prüfe per GET /sessions/{id}: das Feld txid zeigt auf den echten On-Chain-Transfer.
- Kunde wurde doppelt belastet
- Sollte nicht passieren. Jede Session hat genau eine Settlement-addressIn; eine zweite Zahlung an dieselbe Adresse wird bei uns eine eigene Session, und nur die erste wird deiner Bestellung gutgeschrieben. Falls doch: Screenshot der beiden Transaktions-IDs (Etherscan oder Polygonscan) + Session-ID an support@peptide-pay.com. Wir erstatten das Duplikat aus unserer Kasse.
- 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.