Accept crypto from your own backend, with one API call
The Payments API is the programmatic way in. You POST a payment, Suward mints a deposit address that belongs to that one order, and you keep your own checkout, your own database, your own UI. Keep your own checkout and UI — create a payment, get a per-order address, done.
One POST, one payment, one address
One API to accept crypto — compliant and final. A payment is one asset on one network. You send the amount as a string, the asset, your own externalId for the order, and a webhookUrl. The response hands back a deposit address that exists for this payment and nothing else, so two orders never share an address and reconciliation stays trivial. Show that address however you like: render a QR on your checkout, drop it into an invoice email, or pass it to a point-of-sale screen.
{ // your order ID — doubles as idempotency key "externalId": "order-10472", "asset": "USDT_BASE", // smallest unit — USDT has 6 decimals, so 49900000 = 49.90 USDT "amount": "49900000", // unique deposit address for this payment "address": "0x1f3a…9c2e", "status": "PENDING", "subStatus": "CREATED"}What the call actually looks like
Create a payment against your project. The key goes in the X-Api-Key header, server-side only, never in the browser.
curl -X POST \ https://api.suward.com/v1/projects/$PROJECT_ID/payments \ -H "X-Api-Key: $SUWARD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": "49900000", "asset": "USDT_BASE", "externalId": "order-10472", "webhookUrl": "https://shop.example.com/webhooks/suward" }'# X-Api-Key: server-side secret — never expose in client code# externalId: idempotency key — same value returns the same payment# amount: smallest unit — USDT has 6 decimals, so 49900000 = 49.90 USDT{ "id": "pay_...", "externalId": "order-10472", "asset": "USDT_BASE", // smallest-unit integer strings (USDT, 6 decimals): // 49900000 = 49.90 USDT, 1000000 = 1.00 USDT (the $1 minimum fee) "amount": "49900000", // send this address to the customer for deposit "address": "0x...", "status": "pending", "subStatus": "created", "fee": "1000000"}Money values are integer strings in the asset’s smallest unit, never floats — for USDT (6 decimals) "49900000" is 49.90 USDT. Handles payments up to 60,000 per charge. The full CreatePaymentRequest also accepts metadata, paymentWindowSeconds, activationFlowSeconds and underpaymentTolerance when you need them. The response carries the address you collect on plus the two-level status pair.
Retries don't double-charge
Pass your externalId as the order key. Send the same externalId twice and Suward returns the payment that already exists instead of opening a second one, so a timed-out request, a retried queue job or a double-clicked button all resolve to the same order. Your own order reference is the idempotency key; the reference you already have does the job.
# First attemptPOST /v1/projects/$PROJECT_ID/payments{ "externalId": "order-10472", "amount": "49900000", ... }→ 200 id: "pay_f4e1a2" status: "PENDING" # Retry (same externalId)POST /v1/projects/$PROJECT_ID/payments{ "externalId": "order-10472", "amount": "49900000", ... }→ 200 id: "pay_f4e1a2" status: "PENDING" Same payment id. No duplicate order.Status delivery, signed and reliable
Status comes to you as a signed Ed25519 webhook. Point a webhookUrl at your endpoint — once per project, or overridden per payment — and Suward posts payment.accepted, payment.success and payment.failed as the order moves; verify each against your public key. Delivery is at-least-once, backed by an outbox written in the same transaction as the state change, so a brief outage never drops an event. Pair it with a redirect to send the payer back to your store after checkout, but always confirm status server-side, never on the redirect alone. Prefer to pull? Poll GET /v1/payments/{paymentId} until a terminal status. Both are live; pick one, and the docs carry the payload, headers and retry schedule.
Accepted now, success when it's irreversible
The status moves PENDING → ACCEPTED → SUCCESS, or lands on FAILED off the happy path. It becomes ACCEPTED as soon as it's safe to act on — credited and safe to release the order — then SUCCESS once it's final and irreversible, ready to withdraw. You read one clear status per payment instead of counting confirmations yourself; the thresholds are tuned per network.
Read status names from GET /v1/paymentStatuses rather than hardcoding them, and your integration survives a future change.
v2 keys you can rotate and revoke
Every server call carries an X-Api-Key scoped to a project. Keys are v2: each has its own rate limit, you rotate them without downtime, and you revoke one by its prefix the moment it leaks. Payments, balances and keys all sit at the project level, so one project's books never bleed into another's.
sk_live_a3f… | 100/min | active |
sk_live_7b2… | 100/min | rotated |
sk_live_9e1… | 50/min | revoked |
7 networks, 21 assets, one price
Charge in USDT, USDC and the native coin of each chain across Ethereum, BSC, Arbitrum, Optimism, Base, Plasma, Polygon. The asset list is driven by backend config and keeps growing. Pricing is one line: 0.4% per payment, minimum $1, plus a fixed network fee per chain. On a $100 charge that floor works out to 1%; on a $10 charge it's 10%; on larger payments the effective rate slides toward 0.4%.
What's live today
Live today: the create-payment call, unique per-order addresses, signed Ed25519 webhooks, idempotency, scoped, rotatable v2 keys, and public test payments to run against.
Build it tonight
From signup to your first confirmed payment in one evening. Try the call in test mode first, no card and no commitment, then point it at production.