4 paths, equal weight: Shopify, WooCommerce, AI builders (Lovable / v0 / Bolt / Cursor), or direct API. Click the one that describes you and we walk through the exact steps — nothing more, nothing less.
Three endpoints, one webhook. That's the whole surface. Use the SDK for Node or hit the raw API from any language.
The SDK is a thin wrapper over the three endpoints. If you prefer raw fetch, skip it — every example below works without the SDK.
/api/v1/checkout/initMint a hosted checkout URL/api/v1/sessions/{id}Poll session status (webhook fallback){your-site}/api/peptidepay-webhookPeptide-Pay → you. HMAC-signed. Mark order paid.// 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);The x-peptidepay-signature header is t=<unix_seconds>,v1=<hex HMAC-SHA256>. Compute HMAC-SHA256(whsec_secret, t + "." + raw_body) and timing-safe-compare to v1. Reject anything older than 5 minutes. Parse the JSON only after verification. Requires a signup account for the whsec_ secret — wallet-only flows ship unsigned.
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 with any future expiry. Point your webhook at an ngrok tunnel to see the signed POST end-to-end.