# KryptoPay — AI Integration Guide (llms.txt) > This file is written for AI coding assistants (Claude, Cursor, Copilot, etc.). > Point your assistant at this URL and ask it to "integrate KryptoPay checkout". > Everything needed to add crypto payments to a website or app is below, with > copy-paste-ready code. Human docs: https://krypto-pay-checkout-widget.vercel.app/docs ## What KryptoPay is A multi-chain crypto checkout. The merchant asks for an amount in **USD**; the customer pays in a supported token on Ethereum, Base, Arbitrum, BNB Chain, Polygon, Apertum, or TRON; KryptoPay handles the conversion, gas, and settlement and pays the merchant a **stablecoin**. Volatile tokens (ETH, BNB, WBTC, BTCB, APTM, PUG, wBTC) are auto-swapped on-chain — the merchant never holds the volatile asset. ## Endpoints & assets (production) - API base: `https://krypto-pay-api-prod.onrender.com` - Widget script: `https://krypto-pay-checkout-widget.vercel.app/dist/kryptopay.js` - Replace both with the merchant's custom domain if they have one. ## Keys (from the KryptoPay dashboard → API Keys) - Publishable key `pk_...` — safe in the browser. Can create intents + open the widget. - Secret key `sk_...` — server-only, never ship to the browser. - Both are sent as `Authorization: Bearer `. ## Supported networks & tokens | Network | chainId | Tokens | |---|---|---| | Ethereum | 1 | USDT, USDC, DAI, ETH, WBTC | | Base | 8453 | USDT, USDC, DAI, ETH | | Arbitrum | 42161 | USDT, USDC, DAI, ETH | | BNB Smart Chain | 56 | USDT, USDC, DAI, BNB, BTCB | | Polygon | 137 | USDT, USDC, DAI | | Apertum | 2786 | wUSDT, APTM, PUG, wBTC | | TRON | 728126428 | USDT, CS | Stablecoins: USDT, USDC, DAI, wUSDT, CS. Everything else is volatile (auto-swapped). --- ## Path A — Client-only (fastest, good for simple sites) Load the script, then open the modal with a publishable key. The widget creates the payment intent for you. ```html ``` ## Path B — Server + client (recommended for real orders) 1. **Server:** create the intent with the secret key. Persist `intent.id` against the order. ```js // Node (any backend works — it's a plain POST) const res = await fetch('https://krypto-pay-api-prod.onrender.com/v1/payment-intents', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.KRYPTOPAY_SECRET}` }, body: JSON.stringify({ amount: '25.00', token: 'USDT', network: 'ethereum', metadata: { orderId: 'ORDER-123' } }), }); const { data: intent } = await res.json(); // intent.id, intent.status ... ``` 2. **Client:** open the widget for that intent. ```js KryptoPay.open({ apiKey: 'pk_live_xxx', apiUrl: 'https://krypto-pay-api-prod.onrender.com', paymentIntentId: intent.id, onSuccess: () => location.assign('/thank-you'), }); ``` 3. **Fulfill on the webhook** (the source of truth — see below), not on `onSuccess`. --- ## `KryptoPay.open(options)` reference - `apiKey` (required) — `pk_...` - `apiUrl` — API base (defaults to localhost in dev; set it in production) - `amount` — USD string, e.g. `'25.00'` (omit if you pass `paymentIntentId`) - `paymentIntentId` — use an intent you created server-side (Path B) - `token`, `network` — optional; omit to let the customer choose - `merchantName`, `metadata` — display + arbitrary data echoed in the webhook - `branding: { brandColor, theme, logoUrl }` — `brandColor` optional (falls back to KryptoPay's brand color); `theme` is `'dark'` | `'light'` - `onSuccess(payment)`, `onFailure(error)`, `onClose()` — callbacks ## Webhooks (fulfillment — the ONLY safe trigger) Configure a webhook endpoint in the dashboard for the `payment.confirmed` event. KryptoPay POSTs JSON to your URL with header: ``` KryptoPay-Signature: t=,v1= ``` Verify it (Stripe-style): HMAC-SHA256 over `` `${t}.${rawBody}` `` with your endpoint secret, compared constant-time, and reject if `t` is older than 5 min. ```js import crypto from 'crypto'; function verify(rawBody, header, secret) { const parts = Object.fromEntries(header.split(',').map((p) => p.split('='))); const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex'); const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; return ok && fresh; } // On valid `payment.confirmed`: mark the order (by payload.data.metadata.orderId or paymentIntentId) as paid & fulfill. ``` Payload shape: `{ id, type: 'payment.confirmed', data: { paymentIntentId, amount, token, network, txHash, payerAddress }, createdAt }`. ## Alternative — confirm on your server (no webhook) If you don't run a webhook, have `onSuccess` ping your own endpoint, then re-check the intent server-side with the SECRET key before fulfilling. Never trust the browser payload directly. ```js // Browser — onSuccess just triggers your endpoint (UI only otherwise) onSuccess: (p) => fetch('/confirm', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentIntentId: p.id }), }); // Your server — verify, then fulfill app.post('/confirm', async (req, res) => { const r = await fetch( `https://krypto-pay-api-prod.onrender.com/v1/payment-intents/${req.body.paymentIntentId}`, { headers: { Authorization: `Bearer ${process.env.KRYPTOPAY_SECRET}` } }, ); const { data } = await r.json(); if (data.status === 'confirmed') fulfillOrder(data.metadata.orderId); res.json({ ok: true }); }); ``` `GET /v1/payment-intents/:id` returns the authoritative status. The webhook is still the most robust path (server-to-server, can't be spoofed); this is the simpler alternative when you can't receive webhooks. ## Payment statuses `pending` → `confirmed` (paid, settled) — plus `expired`, `underpaid`, `failed`, `refunded`. Only `confirmed` means the merchant got paid. ## Hard rules (tell the AI to follow these) 1. Fulfill orders on the **webhook**, never on `onSuccess` (client-side, spoofable). 2. Never put the `sk_...` secret key in browser/client code. 3. Amounts are **USD strings**; KryptoPay does the token conversion. 4. Verify the webhook signature AND the 5-minute timestamp window before trusting it. 5. `metadata` is echoed back in the webhook — use it to tie a payment to your order.