Accepting payments
A payment is a one-time request to collect a fixed amount of a single asset at a unique, single-use deposit address. This guide covers the full lifecycle: creation, checkout, statuses, timing, redirects, and the edge cases.
What you'll have at the end
A server-side flow that creates a payment for one order, shows the customer where to pay, reacts to signed webhooks, and reconciles the result against your own order id. Prerequisites: an API key (Authentication) and an asset id from GET /v1/assets.
Create the payment
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",
"paymentWindowSeconds": 1800,
"webhookUrl": "https://example.com/webhooks/suward"
}'| Field | Notes | |
|---|---|---|
amount | required | Integer string in the asset's smallest unit. Never a float. |
asset | conditional | Required when your project allows more than one asset. One payment, one asset — the customer never picks it. |
externalId | optional | Your order reference. Makes the create call idempotent. |
paymentWindowSeconds | optional | 300–86400. Project default when omitted. |
activationFlowSeconds | optional | 1–3600. Delays the window until the customer opens the checkout. |
underpaymentTolerance | optional | ≥ 0 and < amount, same unit. Default 0. |
webhookUrl | optional | Overrides the project default for this payment only. |
Full field list and response shape → Create payment.
Show the customer where to pay
Two options, same payment object. Redirect to paymentPageUrl and Suward renders the checkout for you (Hosted checkout), or build your own screen from address, amount, and asset. With an activation flow configured, address is null until the payment is activated.
Statuses and transitions
status and subStatus are independent axes — read both. Full model, timings, and amount edge cases live on one page: Payment lifecycle & statuses.
| From | To | Trigger | Webhook |
|---|---|---|---|
Pending / created | Pending / awaitingPayment | Activation — API call or the customer opening the page | — |
Pending / awaitingPayment | Pending / confirming | First transfer detected on-chain | payment.detected |
Pending / confirming | Accepted | Safe confirmations reached; balance credited | payment.accepted |
| Accepted | Success | Finality reached; funds withdrawable | payment.success |
| any earlier stage | Failed | Window closed, cancelled, screening rejected, or invalidated | payment.failed |
| any earlier stage | Pending / complianceHold | Held for compliance review | — |
A dash means no webhook is sent for that transition — the object changes and the next GET shows it. → Webhooks
Sub-statuses you will also see: overpaid, underpaid, expired, cancelled, partiallyPaid, complianceRejected.
Fees
Fees. Set serviceFeePayer and networkFeePayer at creation (merchant by default), and complianceLevel for screening depth. Preview the exact numbers with POST /v1/payments/quote before creating. Full mechanics: Fees & who pays what.
Redirects: returning the customer to your store
Pass redirectConfig at creation and Suward sends the customer back when the payment completes.
{
"redirectConfig": {
"url": "https://example.com/orders/complete",
"params": ["id", "externalId"],
"data": "session-token-abc"
}
}url— the base return URL, up to 2048 characters.params— which identifiers to append:id(the Suward payment id) andexternalId(yours).data— an opaque string passed through unchanged as thedataquery parameter, up to 512 characters. Use it to carry your own session or order token.
The redirect is unsigned
Anyone can craft the return URL. Treat its parameters as hints and always confirm status via
GET /v1/payments/{paymentId} before showing "paid" or releasing anything.
Metadata
Metadata. Attach up to 4 KiB of your own JSON to a payment at creation; it is stored and echoed back unchanged on the payment and its webhooks, never sent on-chain. Use it to correlate payments with your own records without a lookup table.
{
"metadata": {
"orderId": "42",
"customerId": "cust_881",
"cart": ["sku-19", "sku-22"]
}
}Edge cases worth handling
- Duplicate create calls. Retries with the same
externalIdnever create a second payment; a duplicate returns409. Fetch the existing payment instead of creating another. - Underpayment. Set
underpaymentToleranceif dust and gas rounding cost you more in support than in shortfall. - Overpayment. The excess is credited; the payment completes as
overpaid. Decide once whether you refund, credit, or ignore it. - Expiry with a partial transfer. Ends as
failed/partiallyPaidwith the received amounts on the payment — a support case, not a silent loss. - Cancellation.
POST /v1/payments/{paymentId}/cancelworks only before finality. - Multiple transfers. One payment can receive several transfers; all of them are listed at
GET /v1/payments/{paymentId}/transactions.
Something unclear? payments@suward.com — we treat every integration question as a documentation defect.