Einer dieser vier beschreibt dich. Klick drauf und wir führen dich durch die exakten Schritte für deinen Stack — nicht mehr, nicht weniger.
Drei Endpunkte, ein Webhook. Das ist die ganze Fläche. Nutz das SDK für Node oder hau die rohe API aus jeder Sprache raus.
Das SDK ist ein dünner Wrapper über den drei Endpunkten. Wenn du rohes fetch bevorzugst, überspring es — jedes Beispiel unten funktioniert ohne SDK.
/api/v1/checkout/initGehostete Checkout-URL erstellen/api/v1/sessions/{id}Session-Status pollen (Webhook-Fallback){your-site}/api/peptidepay-webhookPeptide-Pay → du. HMAC-signiert. Bestellung als bezahlt markieren.// Create a checkout session and redirect the customer.
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(),
},
body: JSON.stringify({
amount_cents: 5000, // 50.00 EUR
currency: 'EUR',
metadata: { order_id: 'ord_123' },
success_url: 'https://mysite.com/order/ord_123',
cancel_url: 'https://mysite.com/cart',
}),
});
const { url } = await res.json();
return Response.redirect(url, 303);Der x-peptidepay-signature-Header ist t=<unix_seconds>,v1=<hex HMAC-SHA256>. Berechne HMAC-SHA256(whsec_secret, t + "." + raw_body) und timing-safe-vergleiche mit v1. Alles älter als 5 Minuten ablehnen. Die JSON erst nach Verifizierung parsen. Erfordert ein Signup-Konto für das whsec_-Secret — Wallet-only-Flows werden unsigned ausgeliefert.
import crypto from 'node:crypto';
export async function POST(req: Request) {
const raw = await req.text(); // MUST be raw bytes
const sigHeader = req.headers.get('x-peptidepay-signature') ?? '';
// Header format: t=<unix_seconds>,v1=<hex HMAC-SHA256>
const [tPart, v1Part] = sigHeader.split(',');
const t = tPart?.split('=')[1];
const v1 = v1Part?.split('=')[1];
if (!t || !v1) return new Response('bad sig', { status: 400 });
// Reject replays > 5 min old.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) {
return new Response('stale', { status: 400 });
}
const expected = crypto
.createHmac('sha256', process.env.PEPTIDEPAY_WEBHOOK_SECRET!)
.update(`${t}.${raw}`) // ts + '.' + body
.digest('hex');
if (v1.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'))) {
return new Response('invalid sig', { status: 401 });
}
const event = JSON.parse(raw); // parse only after verify
if (event.event === 'order.paid') {
await markOrderPaid(event.order_id, event.txid); // idempotent on session_id
}
return new Response('ok', { status: 200 });
}// app/api/checkout/route.ts
export async function POST(req: Request) {
const { product_slug, quantity } = await req.json();
const product = await db.product.findUnique({ where: { slug: product_slug } });
if (!product) return new Response('not found', { status: 404 });
const order = await db.order.create({
data: { product_id: product.id, quantity, status: 'pending' },
});
const r = 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': order.id,
},
body: JSON.stringify({
amount_cents: product.price_cents * quantity,
currency: 'EUR',
metadata: { order_id: order.id },
success_url: `${process.env.PUBLIC_URL}/order/${order.id}`,
cancel_url: `${process.env.PUBLIC_URL}/cart`,
}),
});
const { url } = await r.json();
return Response.json({ url });
}
// app/api/peptidepay-webhook/route.ts — verify HMAC, mark order paid.
// app/order/[id]/page.tsx — success page polls /api/order/[id]
// app/api/order/[id]/route.ts — returns order.status for the poller.4242 4242 4242 4242 mit beliebigem zukünftigem Ablaufdatum. Zeig deinen Webhook auf einen ngrok-Tunnel, um den signierten POST end-to-end zu sehen.