- Standing rules — a persistent
from→toconfiguration that fires automatically on every matching deposit. The right model for recurring on-ramp and off-ramp flows. - One-shot conversions — a discrete per-transaction operation, initiated explicitly per conversion. The right model for on-demand or price-locked execution.
Not what you’re looking for? Conversions handle asset exchange with automatic fund movement across rails. If you
need to move funds between accounts in the same asset without conversion, see
Transfers. If you want to execute a pure trade against a locked
quote without managing rails, see Swap. For fiat-to-crypto purchases via a
hosted checkout UI, see Ramp.
How it works
The Conversions API is built around two complementary services:- PWS Conversion API — handles the rails. Pulls funds from a source (external wallet, bank account, or prefunded balance) and pushes the converted asset to a destination.
- PWS Swaps API — handles pricing and trade execution. Called explicitly when you want to lock a rate before funds move, or implicitly at settlement time when you don’t.
Part 1 — Standing conversion rules
A standing rule is a persistentfrom → to configuration. PWS provisions a dedicated inbound endpoint — an IBAN, sort code, or deposit address — and any matching credit automatically triggers a conversion and settlement. Rules remain active and reusable as long as the user’s KYC profile is valid.
Rule matching
Rules use strict 1:1:1 matching. A credit fires a rule only if it lands on that rule’sinbound.id, its asset matches from.asset.symbol, and (when set) its amount matches from.asset.amount exactly. Credits that don’t match any rule land in the account balance rather than misrouting.
Creating a rule — fiat on-ramp (EUR → USDG)
An end customer sends EUR via SEPA and automatically receives USDG on Solana.inbound block with the provisioned IBAN and BIC:
inbound.routing.iban and inbound.routing.bic with the end customer as their payment instructions. Every SEPA credit to that IBAN fires the rule and produces a new Conversion. current_uses increments on each successful firing.
Inbound routing fields by rail
via | Fields to share with the end customer |
|---|---|
sepa, swift | inbound.routing.iban + inbound.routing.bic |
fps | inbound.routing.sort_code + inbound.routing.account_number |
fedwire, ach | inbound.routing.routing_number + inbound.routing.account_number |
| Any crypto network | inbound.address |
Creating a rule — crypto off-ramp (USDC/Ethereum → EUR)
An end customer sends USDC on Ethereum and automatically receives EUR via SEPA to their bank account.inbound.address on Ethereum. Share it with the end customer — any USDC deposit to that address triggers the rule and initiates SEPA payout.
Fiat destinations: The beneficiary name on the bank account must match the user’s KYC identity. Third-party bank
accounts are not permitted.
Flexible asset intake for off-ramp rules: Because EVM-compatible networks share address formats, a deposit address
generated for one asset may receive other tokens on the same or compatible networks. The system evaluates the actual
deposited asset at detection time and proceeds with conversion if the asset is supported. Deposits that cannot be
converted to the rule’s output fiat currency are rejected.
Multi-network support
Rules enforce strict 1:1:1 matching, so accepting the same asset on multiple networks requires one rule per network. Each rule gets its own inbound address, its owncurrent_uses counter, and can be paused or deleted independently — making reconciliation and per-network reporting straightforward.
To accept USDC on Ethereum, Solana, and Polygon, create three rules identical except for from.via:
Rule lifecycle
| State | Meaning |
|---|---|
active | Rule is live. Deposits are processed normally. |
paused | Deposits are not processed. Can be reactivated by the partner or customer. |
disabled | Disabled by the system due to compliance or risk controls. Reactivatable once the underlying issue is resolved. |
deleted | Permanently deactivated. Cannot be reactivated. |
Monitoring rule-triggered conversions
When a rule fires, PWS emits aconversion.created webhook. When the withdrawal confirms, conversion.completed is emitted. To poll instead, filter by conversion_rule_id and set page_size: 1 to fetch the latest conversion for a given rule:
Part 2 — One-shot conversions
One-shot conversions are discrete per-transaction operations. There is no standing rule — each conversion is initiated explicitly viaPOST /v1/accounts/{account_id}/conversions. There are three variants depending on the funding source and whether you want to lock the exchange rate.
Variant A — Balance-sourced
Use this when the account has a prefunded balance and you want to convert on demand without any inbound push. The conversion executes synchronously onPOST and returns status: completed immediately.
inbound block is returned. The response includes to.amount, fees, and chain_references.withdraw_txid once the withdrawal is confirmed.
Variant B — Push-sourced, indicative price
Use this when you want PWS to provision a one-time deposit address and execute at market rate when the deposit arrives. Simpler than the price-locked flow and appropriate when rate certainty is not required.status: pending_deposit and a single-use inbound.address with an expires_at. Push funds to that address before expiry. Once the deposit lands, the conversion progresses through converting → settling → completed.
Variant C — Push-sourced, price-locked
Use this when you want to show the end customer a guaranteed rate before they send funds. Requires two API calls: first lock a quote from the Swaps API, then create the conversion referencing it. Step 1 — Lock a quote:quote_id, the locked rate, fees, and expires_at (typically ~2 minutes).
Step 2 — Create the conversion referencing the quote:
inbound.address before the quote expires. PWS executes against the locked quote and withdraws SOL.
Quote expiry: The locked rate is valid for approximately 2 minutes. The balance-sourced variant (A) is better
suited for price-locked flows when a prefunded balance is available, since it avoids the timing pressure of an
external push.
Webhook events
| Event | Payload |
|---|---|
conversion.created | Full Conversion resource |
conversion.deposit.detected | Conversion with chain_references.deposit_txid |
conversion.completed | Conversion with to.amount, fees, and rate |
conversion.failed | Conversion with failure_reason |
conversion.expired | Push-sourced conversion that timed out |
wallet.whitelisted | Wallet with status transition (pending → approved or rejected) |
API reference
| Endpoint | Description |
|---|---|
POST /v1/accounts/{account_id}/wallets/whitelist | Verify and whitelist a destination wallet (required before use) |
POST /v1/accounts/{account_id}/deposits | Fund the account balance for balance-sourced conversions |
POST /v1/accounts/{account_id}/conversion-rules | Create a standing conversion rule with a dedicated inbound endpoint |
POST /v1/accounts/{account_id}/conversions | Create a one-shot conversion |
GET /v1/accounts/{account_id}/conversions | List conversions, filterable by rule, status, or end-customer |
GET /v1/conversions/{conversion_id} | Get a single conversion by ID |
POST /v1/accounts/{account_id}/quotes | Lock a rate before creating a price-locked conversion |