Hosted checkout

The hosted checkout is a ready payment page. You create a payment via the API and redirect your customer to its paymentPageUrl — that’s the integration.

The page shows the exact amount, the deposit address, a QR code, a mempool indicator, and a countdown for the payment window; when the payment completes, the customer returns to your store.

The flow

  1. Create a payment server-side (→ Accepting payments). One payment, one asset, one exact amount — you set the asset at creation; your own checkout decides which assets to offer before this call.
  2. Redirect to paymentPageUrl. The URL is valid for the payment's lifetime. No secrets touch the browser.
  3. The customer pays. The page tracks the deposit live — detection, confirmations, completion.
  4. You receive signed webhooks at every stage transition; fulfill on payment.accepted. → Webhooks
  5. The customer returns. Configure redirectConfig (base url, optional id / externalId query params, and an opaque data string) to send them back to your store with a "Return to store" button and auto-redirect. The redirect is a plain browser navigation — treat its params as hints and confirm the authoritative status via GET /v1/payments/{paymentId} before showing "paid."
terminal
curl -X POST https://api.suward.com/v1/payments \
  -H "X-Api-Key: $SUWARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "10000000",
    "asset": "USDT_ARBITRUM",
    "externalId": "order-42",
    "activationFlowSeconds": 600,
    "redirectConfig": {
      "url": "https://example.com/orders/complete",
      "params": ["id", "externalId"]
    }
  }'
checkout.ts
// server-side handler — the browser only ever sees the redirect
const payment = await suward.payments.createPayment({
  amount: "10000000",
  asset: "USDT_ARBITRUM",
  externalId: order.id,
});
 
return Response.redirect(payment.paymentPageUrl, 303);

Timing that matches a real cart

Set activationFlowSeconds so the payment window doesn't start counting until the customer actually opens the checkout page — created but unopened payments don't burn their window in your cart flow. → Payment lifecycle & statuses

The page activates the payment itself; if you drive your own flow, POST /v1/payments/{paymentId}/activate is public and customer-facing, and returns the limited customer view of the payment. → Activate payment

What the customer sees, and what your server trusts

SurfaceReadsTrust it for
Hosted pageThe customer view of the payment — no API key involvedShowing the customer where they stand
Return redirectQuery params you configuredRouting the browser. Nothing else.
WebhookSigned event with the full payment objectFulfilment, after Ed25519 verification
GET /v1/payments/{id} with your keyThe authoritative merchant viewAnything you would regret getting wrong

See it live

Our demo store runs on this exact integration — pick an item and pay with free test tokens, end to end; its source code is public as a reference implementation. → demo.suward.com

Next: Test payments to run the flow without real money · Going live checklist when it works.

Something unclear? payments@suward.com — we treat every integration question as a documentation defect.