### Node SDK: create session, retrieve session, verify webhook Source: https://peptide-pay.com/docs Use the Node SDK to create and retrieve checkout sessions and verify webhooks. Requires installing the package with npm and setting the QIST_API_KEY and QIST_WEBHOOK_SECRET environment variables. The webhook handler uses express.raw and throws on an invalid signature; the only delivered event is 'order.paid'. ```javascript // npm install github:kinerette/peptide-pay-sdk // The published package is still named "peptide-pay"; we alias the client // to Qist here so your code reads cleanly. import { PeptidePay as Qist } from 'peptide-pay'; const qist = new Qist(process.env.QIST_API_KEY); // Create a session const session = await qist.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 qist.sessions.retrieve(session.id); // Verify + parse a webhook (throws on invalid signature) app.post('/webhooks/qist', express.raw({ type: '*/*' }), (req, res) => { const event = qist.webhooks.constructEvent( req.body, req.headers['x-peptidepay-signature'], // signature header is unchanged process.env.QIST_WEBHOOK_SECRET, ); // event.event === 'order.paid' (currently the only event delivered) res.sendStatus(200); }); ``` -------------------------------- ### GET /api/v1/providers Source: https://peptide-pay.com/docs Retrieves the list of available payment providers. ```APIDOC ## GET /api/v1/providers ### Description Lists available on-ramp providers that can be pinned via the provider field in checkout init. ### Method GET ### Endpoint https://pay.qistdigital.com/api/v1/providers ### Response #### Success Response (200) No response fields are documented in the source. ``` -------------------------------- ### GET /api/v1/sessions/{id} Source: https://peptide-pay.com/docs Retrieves a session's current status and details, useful for polling or webhook fallback. ```APIDOC ## GET /api/v1/sessions/{id} ### Description Use this as a webhook fallback, or to hydrate a success page after redirect. Server-side re-checks our settlement layer on every call — cheap (<200ms), so polling every 3-5s is fine. ### Method GET ### Endpoint https://pay.qistdigital.com/api/v1/sessions/{id} ### Parameters #### Path Parameters - **id** (string) - Required - Session id. ### Response #### Success Response (200) - **id** (string) - Session id. - **status** (string) - pending | paid | expired | failed. - **amount** (integer) - Original amount in cents. - **currency** (string) - Original currency. - **paid_at** (string|null) - ISO 8601 when the on-chain settlement completed. - **paid_provider** (string|null) - Which provider actually processed payment (can differ from the requested one). - **txid** (string|null) - Polygon settlement txid. Link with polygonscan.com/tx/{txid}. - **expires_at** (string) - ISO 8601 expiry. #### Response Example { "id": "cs_...", "status": "paid", "amount": 5000, "currency": "EUR", "paid_at": "2025-01-01T00:00:00Z", "paid_provider": "moonpay", "txid": "0x...", "expires_at": "2025-01-01T00:00:00Z" } ``` -------------------------------- ### Webhook POST delivery with x-peptidepay-signature header Source: https://peptide-pay.com/docs Example webhook delivery from Peptide Pay. The POST includes the x-peptidepay-signature header (HMAC-SHA256, hex) and a JSON body with event, session_id, order_id, address_in, status, amount, currency, txid, paid_at, and attempt fields. Always parse the raw request body for signature verification; re-serializing JSON reorders keys and breaks the HMAC. ```http 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 } ``` -------------------------------- ### POST /api/v1/checkout/init Source: https://peptide-pay.com/docs Creates a checkout session and returns a hosted checkout URL. ```APIDOC ## POST /api/v1/checkout/init ### Description Mints a hosted checkout URL. The customer opens it, pays with card or crypto, PeptidePay settles to your wallet in USDC, webhook fires. ### Method POST ### Endpoint https://pay.qistdigital.com/api/v1/checkout/init ### Parameters #### Request Body - **amount_cents** (integer) - Required - Amount in the smallest unit of currency (cents). Range 100 – 10 000 000. - **currency** (string) - Required - ISO 4217 code. Supported: EUR, USD, GBP, CAD, AUD, CHF. - **wallet** (string) - one-of - USDC wallet on Polygon (0x + 40 hex). Required unless authenticated via Bearer key. - **customer_email** (string) - Optional - Shown in the checkout UI and forwarded to the on-ramp for KYC reuse. - **success_url** (url) - Optional - Redirect after successful payment. http/https only. - **cancel_url** (url) - Optional - Redirect if the customer abandons checkout. - **webhook_url** (url) - Optional - POST target for order.paid events. Overrides the dashboard default. - **provider** (string) - Optional - Default 'gateway' (smart picker — recommended). Or pin a specific on-ramp id from GET /providers (e.g. moonpay, revolut, banxa, transak). - **product_name** (string) - Optional - Label shown on the checkout page (max 80 chars). - **metadata** (object) - Optional - Up to 10 string key/value pairs, echoed back in the webhook. Reserved key: order_id. ### Response #### Success Response (200) - **id** (string) - Session id, starts with cs_. - **url** (string) - Hosted checkout URL to redirect the customer to. - **status** (string) - Always "pending" at creation. - **amount** (integer) - Echo of amount_cents. - **currency** (string) - Echo of currency. - **provider** (string) - Echo of provider (defaults to 'gateway'). - **expires_at** (string) - ISO 8601 expiry (24h from creation). - **tracking_number** (string) - Polygon settlement address — matches address_in in the webhook payload, usable with /track for live monitoring. #### Response Example { "id": "cs_...", "url": "https://...", "status": "pending", "amount": 5000, "currency": "EUR", "provider": "gateway", "expires_at": "2025-01-01T00:00:00Z", "tracking_number": "0x..." } ``` -------------------------------- ### Create a checkout session with fetch (Node.js 18+) Source: https://peptide-pay.com/docs Creates a checkout session by POSTing to /api/v1/checkout/init. Requires a Bearer API key (Authorization header) and sends amount_cents, currency, email, success_url, cancel_url, and metadata. The response is a JSON object with id and url; the snippet redirects the customer to the hosted checkout URL with HTTP 303. Use an Idempotency-Key to make retries safe and prevent double-charging. ```javascript // Node.js 18+ const res = await fetch('https://pay.qistdigital.com/api/v1/checkout/init', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.QIST_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); ``` -------------------------------- ### Create checkout session and redirect customer (Node.js route) Source: https://peptide-pay.com/docs Production-ready Node.js checkout route that creates a checkout session and redirects the customer to the hosted checkout. Uses the QIST API key from the environment, sends amount in cents, and expects a JSON response with the session URL. Redirects with HTTP 303 after the session is created. ```typescript // 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://pay.qistdigital.com/api/v1/checkout/init', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.QIST_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/qist-webhook', metadata: { order_id: '1234' }, }), }); const { id, url, tracking_number } = await res.json(); // => { id: 'cs_abc…', url: 'https://pay.qistdigital.com/session/cs_abc…', // tracking_number: '0x…', provider: 'gateway', status: 'pending', … } // Redirect your customer to the hosted checkout. return Response.redirect(url, 303); ``` -------------------------------- ### Fetch live provider matrix with curl and jq Source: https://peptide-pay.com/docs Fetches the live provider matrix from the /api/v1/providers endpoint and extracts id, provider_name, minimum_currency, and minimum_amount for each active provider. The response is cached for 5 minutes at the edge, so call it once at app boot rather than per request. ```bash curl -sS 'https://pay.qistdigital.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. ``` -------------------------------- ### Full local loop with ngrok: test webhook receiver without real payment Source: https://peptide-pay.com/docs Use this terminal workflow to test a webhook receiver without a real on-ramp payment. It exposes a local endpoint with ngrok, creates a test session with a sk_test_ key, and fires a signed order.paid event via the simulator. The response echoes the receiver's HTTP status and the exact signature header for HMAC verification. ```bash # 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://pay.qistdigital.com/api/v1/checkout/init' \ -H "Authorization: Bearer $QIST_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/qist" }' # 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://pay.qistdigital.com/api/v1/test/fire-webhook' \ -H "Authorization: Bearer $QIST_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. ``` -------------------------------- ### Node.js webhook signature verification with crypto.timingSafeEqual Source: https://peptide-pay.com/docs Node.js (Express/Next.js) route handler that verifies the webhook signature. It reads the raw body, parses the x-peptidepay-signature header, rejects stale timestamps (>5 minutes), computes the HMAC-SHA256 with the secret, and uses crypto.timingSafeEqual for constant-time comparison. After verification it parses the event and marks the order paid; dedupe by event.session_id because retries re-fire the same event with an incrementing attempt field. ```javascript // Node.js — Express/Next.js route handler import crypto from 'node:crypto'; const SECRET = process.env.QIST_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'); } ``` -------------------------------- ### Poll session status with waitForPayment (Node.js) Source: https://peptide-pay.com/docs Polls the session endpoint every 4 seconds until the session reaches a terminal state (paid, expired, or failed) or a 15-minute timeout. Returns the session object when paid; throws on expired, failed, or timeout. Use as a webhook fallback or to hydrate a success page after redirect; the endpoint is cheap (<200ms) so polling every 3-5s is fine. ```javascript // 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://pay.qistdigital.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'); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.