Skip to content
Checkout SDK v1.2.1

Accept USDT on any website.

One script tag, one button, one server call. Shoppers scan, confirm with their fingerprint and pay with no gas; you get a signed webhook and an on-chain receipt.

1Load the SDK (browser)

Pin an exact version with Subresource Integrity: versioned files (/sdk/v1.2.1/) never change, and if the bytes ever differ the browser refuses to run them. /sdk/v1/checkout.js always serves the latest 1.x for quick prototypes (no SRI).

<script src="https://whitepay.vercel.app/sdk/v1.2.1/checkout.js"
        integrity="sha384-mYqN+5ZK/kC03JCTRK5Izc2hJQ5p5vbImdK8RYS8X7Gpj3OUG90OdBzuiHvwdF6g"
        crossorigin="anonymous" defer></script>

Using a Content-Security-Policy? Allow https://whitepay.vercel.app in script-src (or put your nonce on the tag), connect-src and img-src.

2Add the button

<whitepay-button order-endpoint="/api/create-order"
                 payload='{"sku":"chai-kit"}'
                 label="Pay 2.50 USDT"></whitepay-button>

<script>
  document.addEventListener("whitepay:paid", (e) => showThanks(e.detail))
</script>
order-endpointYour same-origin route that signs the order (step 3). Cross-origin endpoints are refused.
payloadJSON sent to your endpoint (product id, cart id). Never the price.
labelButton text.
whitepay:created{ entityKey, payUrl, deadline } the order is live on Arkiv, QR shown
whitepay:paid{ entityKey, orderId, txHash, explorer, amount } UI hint only: fulfil from the webhook
whitepay:expired / whitepay:errorQR expired unpaid / something failed ({ message })

It is a standard Web Component, so it works in React, Next.js, Vue (isCustomElement), Svelte, Angular (CUSTOM_ELEMENTS_SCHEMA), WordPress, Shopify themes and plain HTML.

3Sign the order (your server)

import { createWhitePayOrder } from "@whitepay/checkout/server"

const CATALOG = { "chai-kit": "2.50" }            // prices live on the server

app.post("/api/create-order", async (req, res) => {
  const amount = CATALOG[req.body.payload?.sku]
  if (!amount) return res.status(400).json({ error: "unknown product" })
  res.json(await createWhitePayOrder({
    merchantKey: process.env.SHOP_PK,               // your registered shop key, never in the browser
    amount,
    webhook: "https://shop.example/api/webhook",
  }))
})

Your shop key signs merchant, amount, orderId, deadline (EIP-712). The WhitePay router contract only settles that exact amount, to your payout address, once.

4Fulfil from the webhook

import { verifyWhitePayWebhook, verifyWhitePayPayment } from "@whitepay/checkout/server"

app.post("/api/webhook", express.text({ type: "*/*" }), async (req, res) => {
  const auth = await verifyWhitePayWebhook({ rawBody: req.body, headers: new Headers(req.headers) })
  if (!auth.ok) return res.status(401).end()          // forged, tampered or replayed
  if (await db.seen(auth.eventId)) return res.end()   // idempotent
  const { txHash, orderId, amount } = auth.event
  const v = await verifyWhitePayPayment({ txHash, merchant: SHOP, orderId, minAmount: BigInt(amount) })
  if (v.ok) await fulfil(orderId)                      // money confirmed on Whitechain
  res.end()
})
x-whitepay-signatureEIP-191 signature of whitepay-webhook:v1:<timestamp>:<keccak256(body)>
x-whitepay-timestampUnix seconds; rejected if older than 5 minutes
x-whitepay-event-idUnique per payment: de-duplicate on it
Signer0x27d2F6770F18E1C0Eefc129124747407Ef44aBe1 (also at /api/webhook-key)

5Subscriptions (Autopay)

Same button, mode="subscribe". The shopper approves once with their fingerprint; WhitePay charges every period with zero gas and sends you a signed webhook per payment.

<whitepay-button mode="subscribe" order-endpoint="/api/create-subscription"
                 label="Subscribe · 4.99 USDT / month"></whitepay-button>

// server
import { createWhitePaySubscription } from "@whitepay/checkout/server"
app.post("/api/create-subscription", async (_req, res) => res.json(await createWhitePaySubscription({
  merchantKey: process.env.SHOP_PK, title: "Pro plan", amount: "4.99", period: "monthly", cycles: 12,
  webhook: "https://shop.example/api/webhook",   // subscription.started | charged | completed | cancelled | failed
})))
  • One signature: the shopper signs a single EIP-2612 permit to a tiny contract made only for their subscription. Its address commits to every term, so one wallet prompt approves exactly that shop, amount, period and number of payments.
  • How it runs: each upcoming payment is an Arkiv entity whose expiry is its countdown. At 0 the keeper calls the contract's charge(), deletes the entity and creates the next one.
  • Hard limits on-chain: never early, never late (missed windows are skipped), never more than amount × cycles, never to anyone but you.
  • Cancel: one signature switches the subscription's contract off; you can also cancel from your side.
  • Verify charges: the contract emits Charged(cycle, amount); the webhook carries the tx hash.

Security model

  • Price tampering: impossible from the browser. The amount is in the shop-signed order and enforced by the contract.
  • Fake payments: the browser event is only a hint; fulfilment needs a signed webhook and a PaymentSettled event on-chain.
  • Replay: EIP-3009 nonces are single-use on-chain; webhooks expire after 5 minutes and carry an event id.
  • Supply chain: versioned SDK path + SRI hash. The SDK never touches keys or money.
  • XSS in the SDK: every network value is regex-validated and written with textContent; links use noopener noreferrer.
  • SSRF: webhooks only go to https:443 hostnames resolving to public IPs, re-checked before every send, redirects not followed.
  • Abuse: per-IP rate limits, JSON-only writes, body caps, cross-site POST blocking and per-payer relayer limits on WhitePay.
  • Refunds: only from your payout wallet, only to the original payer, capped at the amount paid (on-chain).
Whitechain Sepolia testnet · official USDT 0xcedf…55a7 · receipts on Arkiv · names on ENS