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

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",
    "paymentWindowSeconds": 1800,
    "webhookUrl": "https://example.com/webhooks/suward"
  }'
FieldNotes
amountrequiredInteger string in the asset's smallest unit. Never a float.
assetconditionalRequired when your project allows more than one asset. One payment, one asset — the customer never picks it.
externalIdoptionalYour order reference. Makes the create call idempotent.
paymentWindowSecondsoptional300–86400. Project default when omitted.
activationFlowSecondsoptional1–3600. Delays the window until the customer opens the checkout.
underpaymentToleranceoptional≥ 0 and < amount, same unit. Default 0.
webhookUrloptionalOverrides 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.

FromToTriggerWebhook
Pending / createdPending / awaitingPaymentActivation — API call or the customer opening the page—
Pending / awaitingPaymentPending / confirmingFirst transfer detected on-chainpayment.detected
Pending / confirmingAcceptedSafe confirmations reached; balance creditedpayment.accepted
AcceptedSuccessFinality reached; funds withdrawablepayment.success
any earlier stageFailedWindow closed, cancelled, screening rejected, or invalidatedpayment.failed
any earlier stagePending / complianceHoldHeld 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.json
{
  "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) and externalId (yours).
  • data — an opaque string passed through unchanged as the data query 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.json
{
  "metadata": {
    "orderId": "42",
    "customerId": "cust_881",
    "cart": ["sku-19", "sku-22"]
  }
}

Edge cases worth handling

  • Duplicate create calls. Retries with the same externalId never create a second payment; a duplicate returns 409. Fetch the existing payment instead of creating another.
  • Underpayment. Set underpaymentTolerance if 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 / partiallyPaid with the received amounts on the payment — a support case, not a silent loss.
  • Cancellation. POST /v1/payments/{paymentId}/cancel works 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.