Simulate

Move a payment or a static-wallet deposit one legal transition per API call — no wallet, no gas, no chain, no waiting.

Simulation exists for one reason: writing correct code against a payment lifecycle should not require you to own a wallet and wait for blocks. It is available for test-coin assets only — a live asset is rejected — and it never moves your project balance, on a payment or on a deposit: it drives status and webhooks only.

Simulate a payment

status and subStatus are independent axes and both are required — but simulate only ever advances the payment along a legal transition from its current state, the same graph real chain events and webhooks use. Ask for a transition the payment can't currently make and the call is rejected, not forced.

terminal
curl -X POST https://api.suward.com/v1/payments/pay_3f9a2c/simulate \
  -H "X-Api-Key: $SUWARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "accepted",
    "subStatus": "completed"
  }'

One call is one step. To reach a late stage you chain the calls: on a project with an activation flow the payment goes createdpending/activated (which lands on awaitingPayment) → accepted/completedsuccess/completed; without an activation flow the payment already waits for funds, so the first call is the accepted one. There is no shortcut from awaitingPayment straight to success, or from created straight to accepted.

Optionally pass amount — an integer string in the asset's smallest unit — to simulate the received amount, which is how you reproduce underpayment and overpayment. The response is the full payment object, exactly as the real transition would return it, and the matching webhook fires. → Simulate payment

The branches worth simulating

ScenariostatussubStatusWhat it should do in your code
Activated for paymentpendingactivatedShow the checkout and start the payment window. Only from a payment still in created, and only when the project uses an activation flow; it lands on awaitingPayment with an address.
Transfer seen, confirmingpendingconfirmingShow "payment detected"; fulfil nothing.
Safe to fulfilacceptedcompletedShip, credit, deliver. Idempotently.
Customer underpaidacceptedunderpaidApply your tolerance policy, or open a support case.
Customer overpaidacceptedoverpaidCredit, refund, or ignore — decide once.
Settled and withdrawablesuccesscompletedTreat as money. Release payouts. Only reachable from the matching accepted sub-status.
Window closed unpaidfailedexpiredRelease the cart, notify the customer.
Payment cancelledfailedcancelledRelease the cart, notify the customer.

Some statuses can't be requested directly

created, awaitingPayment, partiallyPaid, complianceHold, and complianceRejected are never valid simulate targets — asking for one returns an error. complianceHold and complianceRejected can still show up in the response: simulating completed/overpaid/underpaid runs the same compliance check a real transfer would, and a hold or a deny verdict lands the payment on complianceHold or complianceRejected instead of the sub-status you asked for.

Simulate a static-wallet deposit

The deposit variant takes the asset and amount, because it is creating a synthetic deposit rather than moving an existing one:

terminal
curl -X POST https://api.suward.com/v1/static-wallets/sw_71b0d4/simulate \
  -H "X-Api-Key: $SUWARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "asset": "TESTSTABLECOIN_OPTIMISM",
    "amount": "25000000",
    "status": "accepted"
  }'

Target stages: detected, accepted, confirmed, invalidated. Only accepted and confirmed send a webhook; detected and invalidated send nothing, which is exactly how they behave on a real chain. Pass txHash and transferIndex to control the synthetic identifiers, or let them be generated. No on-chain activity and no balance credit — this drives the lifecycle, not the ledger. → Simulate static wallet deposit

Test assets only

Both simulate endpoints reject live assets. If you need an end-to-end run with real balance movement, use test tokens on a real network instead. → Test tokens

Use it in your test suite

Because simulation is a plain authenticated POST, it belongs in CI: create a payment, simulate each transition, and assert your handler's side effects. Combine it with eventId deduplication tests — redeliver the same event twice and assert exactly one fulfilment.

payment.test.ts
const payment = await suward.payments.createPayment({
  amount: "100000000",
  asset: "TESTSTABLECOIN_OPTIMISM",
  externalId: `ci-${runId}`,
});
 
if (payment.subStatus === "created") {
  await suward.payments.activatePayment({ paymentId: payment.id });
}
 
await suward.payments.simulatePayment({
  paymentId: payment.id,
  status: "accepted",
  subStatus: "completed",
});
 
expect(await orders.find(`ci-${runId}`)).toMatchObject({ fulfilled: true });

Next: Webhooks — the events your simulation fires · Going live checklist when every branch passes.

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