What We’re Building
A server-side payment flow using Dynamic’s Checkout API. The Checkout API lets you accept a crypto payment from any supported chain and have it settled in a specific token on a specific chain. By the end of this guide you will be able to:- Create a reusable checkout configuration (one-time setup)
- Walk through the full payment flow: create transaction, attach source, get quote, sign, broadcast
- Poll or receive webhooks for settlement completion
- Handle errors and edge cases
fetch.
Prerequisites
- A Dynamic environment with the checkout feature enabled
- An environment API token (
dyn_...) from Developer > API Tokens in the dashboard - A wallet with private key access (for signing)
- Node.js 18+ (or any runtime with
fetch)
Base URL
Overview
The checkout flow is a state machine with eight sequential steps:409.
Authentication
| Context | Header |
|---|---|
| Checkout management (create/update/delete) | Authorization: Bearer dyn_... |
| Transaction creation | None required (JWT optional) |
| Transaction mutations (source, quote, prepare, broadcast, cancel) | x-dynamic-checkout-session-token: dct_... |
| Transaction reads (polling) | None required |
Step 1: Create a Checkout (One-Time Setup)
A checkout is a reusable payment configuration. It defines what token(s) you want to receive and the destination wallet(s). Create it once, then reuse thecheckoutId for every payment.
| Field | Description |
|---|---|
mode | "payment" — receiver specifies the amount. "deposit" — sender specifies the amount |
settlementConfig.strategy | How the best quote is selected: "cheapest", "fastest", or "preferred_order" |
settlementConfig.settlements | Token/chain pairs you want to receive. Each needs a matching destination |
destinationConfig.destinations | Wallet addresses where funds land. chainName must match a settlement entry |
enableOrchestration | Optional. Default true. When false, skips cross-chain settlement orchestration |
Settlement Strategies
| Strategy | Behavior |
|---|---|
"cheapest" | Selects the route with the lowest total cost (gas + fees) |
"fastest" | Selects the route with the fewest steps |
"preferred_order" | Returns the first available quote in the order settlements are listed |
id. Save it — you’ll reuse it for every payment.
Manage checkouts with GET, PATCH, and DELETE on /environments/{environmentId}/checkouts/{checkoutId}.
Step 2: Create a Transaction
Start a new payment. This returns a session token that authenticates all subsequent calls.| Field | Description |
|---|---|
amount | Payment amount as a string |
currency | Currency code (e.g., "USD") |
memo | Optional. Arbitrary JSON metadata for your own correlation |
expiresIn | Optional. TTL in seconds. Default: 3600 (1 hour) |
destinationAddresses | Optional. Override the checkout’s destination for this transaction |
Step 3: Attach Source
Declare which wallet and chain you’re paying from. After this, risk screening runs asynchronously.| Field | Description |
|---|---|
sourceType | "wallet" or "exchange" |
fromAddress | Your wallet address. Required for wallet type |
fromChainId | Chain ID (e.g., "1" = Ethereum, "8453" = Base). Required for wallet type |
fromChainName | Chain family: "EVM", "SOL", "BTC", "SUI". Required for wallet type |
executionState: "source_attached".
Error (403): Blocked by sanctions screening — cancel and retry with a different source.
Step 4: Get a Quote
Specify which token you’re paying with. The API finds the best route to your checkout’s settlement token(s).| Field | Description |
|---|---|
fromTokenAddress | Token contract address. Use 0x0000000000000000000000000000000000000000 for EVM native tokens |
slippage | Optional. Slippage tolerance as a decimal (e.g., 0.005 for 0.5%) |
Quotes expire in 60 seconds. If it expires before you call
/prepare, request a new quote. The quoteVersion increments with each new quote.Step 5: Prepare Signing
Locks in the quote and returns the signing payload. You can optionally request on-chain balance assertions.| Field | Default | Description |
|---|---|---|
assertBalanceForGasCost | false | Verify the wallet has enough for gas. Returns 422 if insufficient |
assertBalanceForTransferAmount | false | Verify the wallet has enough for the transfer. Returns 422 if insufficient |
executionState: "signing" and quote.signingPayload:
| Chain | Fields | Notes |
|---|---|---|
| EVM | evmTransaction (to, data, value, gasLimit) | Standard EVM transaction |
| EVM (ERC-20) | evmApproval (tokenAddress, spenderAddress, amount) | Send approval tx first if present |
| SOL, SUI | serializedTransaction | Base64-encoded serialized transaction |
| BTC | psbt | Base64-encoded unsigned PSBT |
422— Quote expired: go back to Step 4422— Risk not cleared: pollGET /transactions/{id}untilriskStateis"cleared", then retry422— Insufficient balance: response includesrequiredandavailableamounts
Step 6: Sign and Broadcast On-Chain
Usequote.signingPayload to sign and submit the transaction. If evmApproval is present, send the approval transaction first.
sign.ts
Step 7: Record the Broadcast
Report the transaction hash back to the API.executionState: "broadcasted".
Step 8: Wait for Settlement
After broadcast, the backend handles cross-chain settlement automatically. Monitor progress via polling or webhooks.Option A: Polling
| Condition | Meaning |
|---|---|
settlementState === "completed" | Funds delivered |
settlementState === "failed" | Settlement failed |
executionState === "failed" | Execution failed |
executionState === "cancelled" | Transaction cancelled |
executionState === "expired" | Session timed out |
none → routing → bridging → swapping → settling → completed. Same-chain, same-token payments jump directly to completed.
Option B: Webhooks (Recommended)
Set up a webhook to receive events as the transaction progresses:| Event | Action |
|---|---|
settlement.state.completed | Payment is done — fulfill the order |
settlement.state.failed | Settlement failed — inspect data.failure |
execution.state.failed | Execution failed — inspect data.failure |
webhook-handler.ts
Cancelling a Transaction
Cancel any time before broadcast (states:initiated, source_attached, quoted, signing):
executionState: "cancelled". Once cancelled, create a new transaction to retry.
Complete Example
A self-contained TypeScript script that creates a checkout, executes a payment, and polls for settlement:checkout-example.ts
Supported Chains and Native Tokens
The Checkout API supports the following chains (mainnet only). Use these values forchainName, chainId, and token addresses in your settlement config and source attachment.
Chain Reference
| Chain | chainName | chainId | Networks |
|---|---|---|---|
| EVM | "EVM" | Standard EVM chain ID (e.g., "1" for Ethereum, "137" for Polygon, "8453" for Base, "42161" for Arbitrum, "10" for Optimism) | All major EVM networks |
| Solana | "SOL" | "101" | Solana mainnet |
| Bitcoin | "BTC" | "1" | Bitcoin mainnet |
| Sui | "SUI" | "501" | Sui mainnet |
Native Token Addresses
For native tokens (ETH, SOL, BTC, SUI), use any of the accepted addresses below in token address fields:| Chain | Accepted native token addresses |
|---|---|
| EVM | 0x0000000000000000000000000000000000000000 (zero address) or 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee |
| Solana | 11111111111111111111111111111111 (System Program) or So11111111111111111111111111111111111111112 (Wrapped SOL) |
| Bitcoin | 11111111111111111111111111111111 or bitcoin |
| Sui | 0x2::sui::SUI or 0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI |
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 for USDC on Base).
Error Reference
| Status | Cause | Recovery |
|---|---|---|
400 | Invalid request body | Check field formats |
401 | Missing or invalid session token | Verify x-dynamic-checkout-session-token header |
403 | Risk screening blocked | Cancel and retry with a different source |
404 | Resource not found | Verify checkout/transaction IDs |
409 | State conflict or duplicate tx hash | Check executionState and call the correct next step |
422 | Quote expired | Re-quote (Step 4) and retry prepare |
422 | Insufficient balance | Source wallet doesn’t have enough funds |
422 | Risk not cleared | Poll until riskState is "cleared", then retry |
Tips
- Balance assertions: Enable both
assertBalanceForGasCostandassertBalanceForTransferAmountin prepare to catch insufficient balance before signing. - Quote evaluation: Check
quote.fees.totalFeeUsdandquote.estimatedTimeSecprogrammatically before proceeding. Cancel and retry with a different token/chain if fees are too high. - Quote expiry: Quotes last 60 seconds. Sign promptly. You can re-quote multiple times —
quoteVersionincrements with each new quote. - Idempotency: Use the
memofield to store your own idempotency keys (e.g.,{ "orderId": "order_abc123" }). - Error recovery: If your process crashes mid-flow, call
GET /transactions/{id}to check the currentexecutionStateand resume from the correct step. - Session token lifetime: Matches the transaction’s
expiresIn(default 1 hour).
Quick Reference
| Step | Method | Endpoint | Auth |
|---|---|---|---|
| Create checkout | POST | /environments/{envId}/checkouts | API token |
| Create transaction | POST | /sdk/{envId}/checkouts/{checkoutId}/transactions | None |
| Attach source | POST | /sdk/{envId}/transactions/{txId}/source | Session token |
| Get quote | POST | /sdk/{envId}/transactions/{txId}/quote | Session token |
| Prepare signing | POST | /sdk/{envId}/transactions/{txId}/prepare | Session token |
| Record broadcast | POST | /sdk/{envId}/transactions/{txId}/broadcast | Session token |
| Cancel | POST | /sdk/{envId}/transactions/{txId}/cancel | Session token |
| Poll status | GET | /sdk/{envId}/transactions/{txId} | None |
Related
- Token swaps via API — Standalone token swaps without checkout orchestration
- Checkout Flow — JavaScript SDK checkout reference
createCheckoutTransaction— SDK transaction creationsubmitCheckoutTransaction— SDK submit (prepare + sign + broadcast)