Entwickler-Referenz

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.

REST · JSONBearer AuthHMAC-SHA256 WebhooksIdempotency-KeyCORS-fähig

Getting Started

Quickstart (5 Min.)

Drei Schritte: Session erstellen, Kunden redirecten, Webhook behandeln. Das Sample unten ist eine produktionsreife 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);

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:

SchemaWieWann
Bearer TokenAuthorization: Bearer sk_live_…Serverseitig. Hält die Wallet privat.
Wallet im Body{ "wallet": "0x…", … }Statische Sites / Widgets ohne Backend.
Achtung
API-Keys tragen die volle Identität des Merchant-Accounts — behandle sie wie Passwörter. Nie committen, nie in den Browser shippen, bei Leak rotieren unter /app/api-keys.
Tipp
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-Referenz

Base URL: . Alle Endpunkte sprechen JSON, liefern bei Erfolg ein einzelnes Objekt zurück und ein -Objekt bei 4xx/5xx.

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

#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.

Request Body
FeldTypPflichtBeschreibung
amount_centsintegerPflichtBetrag in der kleinsten Währungseinheit (Cent). Bereich 100 – 10 000 000.
currencystringPflichtISO-4217-Code. Unterstützt: EUR, USD, GBP, CAD, AUD, CHF.
walletstringone-ofUSDC-Wallet auf Polygon (0x + 40 Hex). Pflicht, außer bei Auth via Bearer-Key.
customer_emailstringoptionalWird in der Checkout-UI angezeigt und an den On-Ramp für KYC-Wiederverwendung weitergeleitet.
success_urlurloptionalRedirect nach erfolgreicher Zahlung. Nur http/https.
cancel_urlurloptionalRedirect, falls der Kunde den Checkout abbricht.
webhook_urlurloptionalPOST-Ziel für order.paid-Events. Überschreibt den Dashboard-Default.
providerstringoptionalDefault 'gateway' (Smart Picker — empfohlen). Oder eine spezifische On-Ramp-ID aus GET /providers (z.B. moonpay, revolut, banxa, transak) pinnen.
product_namestringoptionalLabel auf der Checkout-Seite (max. 80 Zeichen).
metadataobjectoptionalBis zu 10 String-Key/Value-Paare, die im Webhook zurückgespiegelt werden. Reservierter Key: order_id.
Response (200 OK)
FeldTypPflichtBeschreibung
idstringoptionalSession-ID, beginnt mit cs_.
urlstringoptionalGehostete Checkout-URL, auf die der Kunde redirectet wird.
statusstringoptionalImmer "pending" bei Erstellung.
amountintegeroptionalEcho von amount_cents.
currencystringoptionalEcho der Währung.
providerstringoptionalEcho des Providers (Default 'gateway').
expires_atstringoptionalISO-8601-Ablaufdatum (24h ab Erstellung).
tracking_numberstringoptionalPolygon-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);
Tipp
Gib mit, um Retries sicher zu machen. Wir spielen die gecachte Response für 24h auf demselben Key zurück, statt eine neue Session zu minten (verhindert Doppel-Charges bei instabilen Netzwerken).
GEThttps://peptide-pay.com/api/v1/sessions/{id}

#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.

Response
FeldTypPflichtBeschreibung
idstringoptionalSession-ID.
statusstringoptionalpending | paid | expired | failed.
amountintegeroptionalOriginalbetrag in Cent.
currencystringoptionalOriginalwährung.
paid_atstring|nulloptionalISO 8601, wann das On-Chain-Settlement abgeschlossen wurde.
paid_providerstring|nulloptionalWelcher Provider die Zahlung tatsächlich abgewickelt hat (kann vom angefragten abweichen).
txidstring|nulloptionalPolygon-Settlement-txid. Verlinken mit polygonscan.com/tx/{txid}.
expires_atstringoptionalISO 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');
}
GEThttps://peptide-pay.com/api/v1/providers

#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.

Response
FeldTypPflichtBeschreibung
providers[].idstringoptionalProvider-Key (als `provider` in /checkout/init übergebbar).
providers[].provider_namestringoptionalMenschliches Label für das Dropdown.
providers[].statusstringoptional'active' (an diesem Endpunkt immer auf active gefiltert).
providers[].minimum_currencystringoptionalISO-Code des Mindestbetrags.
providers[].minimum_amountnumberoptionalNiedrigster 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

EventWann
order.paidOn-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.

Tipp
Wallet-only-Flows (kein Signup, kein ) liefern den Webhook unsigned — du solltest trotzdem validieren, dass zu einer Session passt, die du erstellt hast. Für signierte Deliveries registriere dich unter /signup, um dein Secret zu bekommen.
// 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');
}
Achtung
Nimm immer constant-time compare für deine Sprache: (Node), (Python), (PHP), (Ruby). Ein plain leakt den HMAC Byte für Byte an einen Timing-Attacker.

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.

Tipp
Mach deinen Handler idempotent. Dedupe auf — der Retry könnte ein paid-Event nochmal feuern, das du schon verarbeitet hast.

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
// 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

Volle Types, Webhook-Helper, automatische Retries.

Direkt fetch()funktioniert immer
beliebige Sprache

Ein POST, ein GET. Keine Lib nötig.

Tipp
Python-, PHP-, Ruby- und Go-SDKs sind auf der Roadmap. Bis sie shippen, sind die rohen //-Samples oben die kanonische Referenz — wir brechen sie nicht.

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.

ZahlungsmethodeDu zahlstKunde zahlt
Karte / Apple Pay / Google Pay3%~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.

400
Ungültiges JSON oder fehlendes Feld
Body fehlerhaft, amount nicht numerisch, wallet keine 0x-Adresse, currency nicht unterstützt.
401
Ungültiger oder widerrufener API-Key
Bearer Token löst nicht zu einem Merchant auf. Rotiere ihn unter /app/api-keys.
403
Falsche Callback-Signatur
Intern — unser Settlement-IPN hat den Webhook-Receiver ohne korrekte Per-Session-Signatur getroffen. Kein merchantseitiger Fehler im Normalbetrieb.
404
Session nicht gefunden
Falsche ID, oder die Session wurde gepruned (> 90d nach terminalem State).
429
Rate Limit überschritten
60 req/min/IP auf init, 30 req/min/IP auf select. Retry-After-Header inklusive. Kontaktiere den Support für höhere Tiers.
502
Upstream nicht verfügbar
Settlement-Netzwerk temporär degraded. Retry in 30s mit dem gleichen Idempotency-Key. SLA-Ziel: 99.5%+.

Troubleshooting

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.