Payments API

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.

API

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.

order-10472 → unique address
{
// 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"
}
Code

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
200 OK · PaymentResponse
{
"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.

Idempotency

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.

POST /v1/payments (×2)
# First attempt
POST /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

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.

Lifecycle

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.

Payment status
PENDINGACCEPTEDSUCCESS
PendingPENDING
SafeACCEPTED
FinalizedSUCCESS
API Keys

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.

Project API keysrate · status
sk_live_a3f…100/minactive
sk_live_7b2…100/minrotated
sk_live_9e1…50/minrevoked
Revoke by prefix · rotate without downtime
Networks & pricing

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%.

EthereumBSCArbitrumOptimismBasePlasmaPolygon
0.4%per payment · minimum $1
Roadmap

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.

create-paymentunique address per orderEd25519 webhooksidempotency via externalIdrate-limited v2 keystest payments

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.