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.
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 created → pending/activated (which lands on awaitingPayment) → accepted/completed → success/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
| Scenario | status | subStatus | What it should do in your code |
|---|---|---|---|
| Activated for payment | pending | activated | Show 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, confirming | pending | confirming | Show "payment detected"; fulfil nothing. |
| Safe to fulfil | accepted | completed | Ship, credit, deliver. Idempotently. |
| Customer underpaid | accepted | underpaid | Apply your tolerance policy, or open a support case. |
| Customer overpaid | accepted | overpaid | Credit, refund, or ignore — decide once. |
| Settled and withdrawable | success | completed | Treat as money. Release payouts. Only reachable from the matching accepted sub-status. |
| Window closed unpaid | failed | expired | Release the cart, notify the customer. |
| Payment cancelled | failed | cancelled | Release 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:
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.
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.