Skip to main content

Prerequisites

  • Payward Services API credentials (see Authentication Guide)
  • A verified user with at least one account
Deposit addresses are issued by Payward and managed under /v1/accounts/{account_id}/funds/deposits/addresses. Withdrawals use a registered funding address or a supported linked bank account. Manage registered addresses under /v1/accounts/{account_id}/funds/addresses and select the destination when creating the withdrawal.

Enrich funding method identifiers

Use Look up funding method metadata to resolve public names, assets, networks, payment rails, and reported requirements for the selected account. Batch method IDs using repeated query parameters. Supply 1–25 entries, counted before duplicate removal:
Alternatively, pass one network_id to resolve its methods. A network can return methods for multiple assets and directions. Supply either method_ids or network_id.
Example crypto metadata:
Network details are optional independently. For example, a token’s contract_address and on_chain_asset_symbol can be present without an explorer. Confirmations can be a numeric string or a network-specific description. Explorer strings can contain transaction URL templates. Fiat metadata can have no network and can return payment_rails: [], including for SEPA methods. The following illustrative entries show a reported payment rail and a bank-link requirement:
See the API reference for field definitions, filtering, and pagination.
Metadata does not establish transaction availability. Use List deposit methods and List withdrawal methods to discover supported operations, withdrawal limits for limits, and Calculate funding fees for fee quotes.

Deposits

Deposit workflow

1

List deposit methods

Query available deposit methods for the receiving account and asset.GET /v1/accounts/{account_id}/funds/deposits/methods?asset_symbol={asset_symbol}
2

Check deposit limits

Retrieve the current limits for each available method.GET /v1/accounts/{account_id}/funds/deposits/limits/{asset_symbol}
3

Prepare the selected method

Fiat and crypto deposits use different preparation steps.

Fiat deposits

Bank transfer: If activation_status is needed, request activation once. If it is pending, wait and list the methods again. When it is completed, not_needed, or omitted, retrieve the bank-transfer instructions. Display every returned field so the user can make the transfer from their bank.Linked bank account: Methods with bank_link in requirements use a linked account instead of bank-transfer instructions. Link the bank account or reuse an active link, then create the deposit to request the funds. Linking alone does not move funds.

Crypto deposits

Deposit address: List existing addresses and reuse a suitable one. If none is available, claim an address. Display the address and any required tag or memo so the user can send the deposit.Bitcoin Lightning: Create an invoice for the exact BTC amount instead of claiming an address. The user must pay it before it expires.
4

Track the deposit

Reconcile deposits on the receiving account through GET /v1/accounts/{account_id}/portfolio/transactions?types=deposit.
The examples below use the signed_headers (signedHeaders) helper from the Authentication Guide. For GET requests, build the path with its query string once, then sign it and request it.
Addresses and memos in the examples are illustrative. Do not send funds to them.
Use Create deposit to pull funds from a linked bank account through a supported deposit method. Currently supported: Plaid US (USD). Discover methods for the receiving account and asset before initiating a deposit.
  1. List deposit methods for the receiving account and asset. Select a supported method whose requirements contains bank_link.
  2. Check the deposit limits for the selected method.
  3. Complete the Bank links flow, or reuse an existing link. The account link must have status: active, and its bank link must have status: linked.
  4. Submit the method’s id and the linked account’s account_link_id to POST /v1/accounts/{account_id}/funds/deposits. Use the same receiving account throughout the flow.
Replace the example identifiers with values returned by discovery and Bank Links. Authenticate the request as described in Authentication. An accepted request returns HTTP 202:
Acceptance does not mean settlement. Poll GET /v1/accounts/{account_id}/portfolio/transactions?types=deposit and match the returned reference to the transaction’s reference.id. The Portfolio transaction id is a separate identifier. Bank-link deposits return a reference.
Create deposit is not idempotent. Do not automatically retry after a timeout or an ambiguous error: the deposit may already have been initiated. Check the account’s transactions and contact Payward Services support if the outcome remains unclear.

Step 1: list deposit methods

Query available deposit methods for an asset. Supply asset_symbol as a query parameter, and follow next_page_token to read every page. Use the method’s id as method_id when listing deposit destinations or claiming a crypto address. Method discovery describes availability and method metadata. Retrieve account-specific rolling limits separately in Step 2. Check requirements on the selected method:
  • []: no additional requirements are reported. Follow the applicable activation and deposit-instructions steps.
  • ["bank_link"]: a linked bank account is required. If you do not already have a suitable link, use Create bank link with the returned method id as method_id, then send the user through the hosted linking flow.
This is a method requirement, not the user’s linking status. It remains bank_link even when the user already has a linked account. Do not infer the requirement from the method name or supports_virtual_accounts. Example USD method excerpts:
For fiat methods, continue when activation_status is not_needed or completed, or when the field is omitted. When it is needed, activate the method once in Step 3. When it is pending, do not send another activation request; list the methods again later or offer another usable method.
Most cryptocurrency deposits are free, with minimum deposit amounts varying by asset. A few cryptocurrencies are charged an address_setup_fee (a one-time fee on the user’s first deposit to a new address) or a per-deposit fee.
Use page_token from next_page_token to request the next page. Omit it for the first page.

Response example

Every result includes asset metadata and the customer-facing method display name. Also display network, fee, minimum, activation_status, and network_info.est_confirmation_time when present. The optional maximum is a method’s per-transaction cap, not the account’s current deposit allowance. Pass the method’s id back as method_id when activating a method, listing deposit instructions, or claiming a crypto address. Optional fee, amount, and network fields are omitted when they do not apply.
For fiat methods, supports_virtual_accounts: true means Funding can provide dedicated virtual-account instructions. false does not identify a particular alternative flow, and omission means Funding did not return deposit metadata for the method. Use the deposit-instructions response, not this boolean, to determine which banking fields and payment reference are available.
Compatible funding methods can share an address space and its limits, so the same address can be returned for different assets or networks. method_id selects a context in which the returned address is valid; it does not say how the address was originally claimed. List and reuse an existing address before claiming another.

Step 2: check deposit limits

Retrieve the account-specific limits for the same account and asset. Follow next_page_token on both operations before matching a limit’s method_id to a discovered method’s id. Ignore limit entries with no matching method. An empty limits data array, or no limit entry for a selected method, does not mean deposits are unrestricted. When present, maximum_amount is the largest amount currently permitted after the account’s amount-based limits, in the deposit asset. It excludes the method’s per-transaction maximum and count-based limits, and other controls may still apply. Zero means no amount remains under the aggregate amount limit. maximum_amount is omitted when no amount-based limit applies. Check each period_limits entry as well. amount limits report a remaining amount and its asset; check that asset before comparing amounts because it can differ from the deposit asset. deposit_attempts and successful_deposits report remaining counts. daily, weekly, and monthly are rolling 24-hour, 7-day, and 30-day windows. custom uses period_seconds. A negative remaining value means a rolling limit is already exceeded. A zero remaining count may prevent another deposit even when maximum_amount is positive. Other deposit controls may still apply. Compare the intended amount with the selected method’s minimum and per-transaction maximum, when present, and with the matching maximum_amount and rolling limits. These limits are point-in-time values. Re-check them before initiating a deposit or creating a Lightning invoice when time has passed, the amount has changed, or another deposit may have consumed a limit. If a deposit is rejected for its amount, refresh the limits and let the user adjust the amount or method. See List deposit limits for the full field reference. For example, use the same receiving account and asset for both requests:
The limit entry below matches the Bitcoin method in Step 1 by method_id. Its maximum_amount is in BTC, but its daily amount limit is in USD:
For USD on account WVSD33HRMGSZUBM7, the same requests return the ACH method and its matching limit:

Step 3: activate a deposit method when required

If the selected method’s activation_status is needed, request activation using its id as method_id. A 204 response means Funding accepted the request; it does not mean activation has necessarily completed, and it does not contain deposit instructions.
After the request is accepted, list deposit methods again. Continue to Step 4 when the selected method reports completed. If it reports pending, wait and list the methods again later.
Activation returns 400 Bad Request when the selected account is missing verification information or documents required by the funding method. The error is associated with account_id; when available, its message names the missing requirement, such as address.
If the activation request times out or its outcome is unclear, list the deposit methods again before taking further action. Do not retry if the activation status is pending or completed.

Step 4: list deposit addresses

Retrieve the crypto addresses or fiat bank-transfer instructions available for the selected funding method. Reuse a returned crypto address instead of creating a new one each time.

Response example

Every item has one address_details variant:
  • crypto.address is required for crypto destinations. crypto.tag and crypto.memo are present only when the network requires them.
  • The fiat object contains only the banking fields that apply to the selected method. Fields such as iban, account_number, routing_number, sort_code, bic, swift, name_on_account, and reference are optional individually. When reference is present, include it with the fiat deposit.
  • expire_time is present only when the destination expires.
Use page_token from next_page_token to retrieve the next page.
Completed deposits appear in GET /v1/accounts/{account_id}/portfolio/transactions?types=deposit. Portfolio visibility is asynchronous.

Step 5: claim a crypto deposit address

If Step 4 returns no suitable crypto address, claim one using the method id from Step 1 as method_id. Fiat deposit methods return their instructions from Step 4 and cannot be claimed with this operation.
Compatible methods can share an address limit. List and reuse an existing address before claiming another. If the limit is reached and no existing address is eligible to expire, the API returns 409 deposit_address_limit_reached.
Address claims are not idempotent. Do not retry automatically after a timeout or another response with an unknown outcome. List the account’s addresses before deciding whether to make another claim.
Some networks (e.g., XRP, XLM) require a tag or memo in addition to the address. If tag or memo is present in the response, your UI must display it and instruct the user to include it when sending funds. Deposits sent without the required tag/memo may be lost.

Bitcoin Lightning invoice flow

For the Bitcoin Lightning deposit method, create a BOLT11 invoice for the exact BTC amount the user intends to deposit. Check the selected method’s minimum and maximum, when present, and the account-specific constraints from the deposit-limits endpoint before requesting the invoice. The selected account_id is the account that receives the completed deposit.
A Lightning invoice is single-use, must be paid in full, and cannot be paid after expires_at. Invoice creation is not idempotent. If a request has an unknown outcome, create a new invoice; unpaid invoices expire on their own.

Reusable crypto-address UI flow

Best practices

  1. Always display tag/memo: For networks that require a tag or memo (XRP, XLM, etc.), prominently display it alongside the address. Missing tags/memos can result in lost funds.
  2. Set expectations: Show minimum amounts and network_info.est_confirmation_time from the methods response so users know what to expect before sending funds.
  3. Use fresh responses: Available methods, addresses and limits are user-specific and may change based on account standing, remaining limits, or regional regulations. Fetch fresh data before displaying options rather than relying on cached results.

Deposit webhook events

Register a webhook through POST /v1/webhooks and subscribe to the deposit lifecycle events your integration needs: All deposit lifecycle events contain type, transaction_id, user_id, and account_id. They can also contain amount and fee:
amount is the total deposit amount, inclusive of the fee. Its value can be negative. PWS includes it when both the source value and its asset can be resolved. fee is optional. PWS includes it only when the source provides both the fee value and its asset and the asset can be resolved. This includes an explicitly provided zero fee. If the fee or its asset is unavailable, PWS omits fee rather than assuming that it is zero or that it uses the deposit amount’s asset. The availability of amount and fee is independent of the lifecycle event type. Treat absent fields as unavailable; PWS does not return them as null. Returned and failed deposits also contain a required reason field. PWS normalizes it to one of account_details_mismatch, limit_exceeded, duplicate, account_restricted, or other. Other deposit lifecycle events do not contain reason. A deposit can produce multiple lifecycle events as it progresses. Treat each webhook as a notification and use the Portfolio Transactions endpoint for reconciliation. Deliveries can be repeated, so use the svix-id header as the deduplication key and return a 2xx response after accepting the event. These events describe the lifecycle of a deposit. They are distinct from conversion.deposit_* events, which describe the inbound funding leg of a conversion.

Negative amounts

The value in amount and fee can carry a leading minus. A negative amount can represent a deposit reversal, not additional money credited to the customer. Keep the sign and parse value as a signed decimal. A negative value is not an error.
To reconcile, match the webhook transaction_id to reference.id on the Portfolio transaction whose reference.kind is funding, within the same account.

Withdrawals

Create a fiat or cryptocurrency withdrawal to a saved funding address, or use a linked bank account for a supported USD method. Funding calculates the fee and validates the method, network, destination, limits, activation, account state, and Travel Rule requirements. Quote the fee first when you need to show it to a user before they commit.

Withdrawal workflow

1

List withdrawal methods

Choose a method for the asset and inspect its requirements and required address_fields.GET /v1/accounts/{account_id}/funds/withdrawals/methods?asset_symbol={asset_symbol}
2

Check withdrawal limits

Retrieve the current maximum and rolling limits for each available method.GET /v1/accounts/{account_id}/funds/withdrawals/limits/{asset_symbol}
3

Select a destination

Reuse or register a compatible funding address, or follow the bank-link withdrawal flow for a supported linked-bank method.GET /v1/accounts/{account_id}/funds/addressesPOST /v1/accounts/{account_id}/funds/addresses
4

Quote the withdrawal fee before you display it

Get the fee for a gross amount. If the quote includes a short-lived fee_token, carry it into the next step.POST /v1/accounts/{account_id}/funds/fees
5

Create the withdrawal

Submit the selected scope, destination, public asset symbol, and gross amount.POST /v1/accounts/{account_id}/funds/withdrawals
6

Handle address-ownership verification

If the request returns withdrawal_verification_required, complete a supported Travel Rule verification method.
7

Reconcile through Portfolio

Use the returned withdrawal_id as reference.id with reference.kind=funding.
After an unknown outcome, reconcile through Portfolio before retrying. Use the original idempotency key and unchanged request body for the same account. Account Link eligibility is checked on every attempt; a rejected retry does not establish whether the original withdrawal was accepted.
Use Create withdrawal with a linked bank account for supported USD ACH or RTP methods.
  1. List withdrawal methods for the sending account and USD. Select a supported method and check its limits.
  2. Reuse a suitable Account Link or complete the Bank links flow. Keep the same PWS account throughout.
  3. Submit the selected withdrawal method as scope.method_id and the linked account as destination.account_link_id.
Replace the illustrative identifiers with values returned by discovery and Bank Links. A method’s bank_link requirement does not establish that the method is supported for withdrawal submission or that the account is eligible. See the destination field in Create withdrawal for eligibility restrictions. A 201 response means the withdrawal was accepted; it does not confirm that funds reached the bank account. Reconcile the returned withdrawal identifier through Portfolio.

Step 1: list withdrawal methods

Supply the asset in the required asset_symbol query parameter:
Follow next_page_token until it is absent. Keep the same account and asset_symbol on every page request. The default page size is 20, and the maximum is 25. When you supply page_token, the token’s page size applies and page_size is ignored. Results are refreshed on each request. Use each method’s id as scope.method_id, or its network_id as scope.network_id. Network scope succeeds only when Funding can resolve exactly one eligible method for the requested asset. Otherwise, retry with a specific method ID. See List withdrawal methods for the field reference. Check account constraints separately in Step 2. requirements: ["bank_link"] reports that a linked bank account is required, even if you already have one. An empty array reports no recognized requirement; it does not guarantee eligibility. Use the linked-bank flow only for a supported method. For a registered address, collect its address_fields in Step 3. fee and fee_percentage are optional, indicative fee terms. Missing fields do not mean the withdrawal is free. To get a fee quote for the amount you intend to withdraw, follow Step 4.

Step 2: check withdrawal limits

Retrieve the account-specific limits for the asset after choosing a method. Use only entries whose method_id appears in the withdrawal-methods response for the same account and asset. Compare your intended amount against both the method’s maximum, when provided, and the matching limits entry’s maximum_amount. These values describe different constraints: the method value is its per-transaction cap, while the limits value accounts for available balance and amount-based limits. It does not replace the method cap. For example, a method cap of 0.5 BTC still applies when maximum_amount is 0.75 BTC. Check period_limits too: count-based limits do not reduce maximum_amount, so a withdrawal can still be rejected when both amount bounds look sufficient. All values are point-in-time and can change before submission. See List withdrawal limits for the full field reference.

Check funds held from withdrawal

Some deposits, including ACH bank transfers, are credited to the account before you can withdraw the funds. For example, an ACH deposit can have a seven-day withdrawal hold. The withdrawal limit uses the balance available for withdrawal, so maximum_reason: "balance" can accompany a maximum below the account balance. Use the withdrawal-holds endpoint in the API reference to see the account’s active restrictions. Select the reference for your environment:
  • asset identifies the asset whose balance is held.
  • equivalent_amount is the held funds’ current value in USD, including when asset is a cryptocurrency. It is not the quantity of the original asset.
  • release_at is the scheduled end of this restriction. A hold can end earlier, including when a deposit is returned. Other restrictions may still prevent withdrawal afterward. Refresh the holds and withdrawal limits before submitting.
  • type identifies a deposit hold or another kind of restriction. For deposit holds, use deposit_transaction_id, when present, to match the deposit webhook’s transaction_id.
Results are ordered by scheduled release time. Follow next_page_token to read additional pages. When continuing with a page token, omit page_size or reuse the previous value. A different value returns a 400 error for page_size. Each page reflects the active holds at request time, so holds that end between requests disappear from the listing. Holds created or rescheduled to release before the last returned hold are not included in later pages. Restart the listing to see them. If the API returns 400 with a page_token cause asking you to restart, omit the token and start again. The funds.withdrawal_held webhook describes a submitted withdrawal waiting for review or checks. Use the holds endpoint for restrictions on the account’s funds before creating a withdrawal.

Step 3: select or create a funding address

List the account’s funding addresses and reuse a compatible destination. Register a new address only when no suitable address exists. Creating, renaming, or deleting a funding address requires the Add withdrawal addresses API key permission.
Funding-address creation is not idempotent. If the request has an unknown outcome, list the account’s funding addresses before creating another one. Do not automatically retry.

List existing funding addresses

The first request can include page_size and either method_id or network_id. To continue, send the returned page_token and the same filter. Omit page_size; the token preserves it. The token is bound to the account and filter. Pass method_id when selecting an address for a particular withdrawal method; omitting both filters returns every funding address on the account. Because scopes nest (method → network → network group), a matching address may be registered more broadly and report a network_id or network_group_id in scope instead of the method_id you filtered on. An empty method_id is rejected as a validation error, not treated as no filter. Alternatively, filter by network_id for addresses registered for that network or its network group. This excludes addresses registered for individual methods. Do not supply both filters.

Create a funding address

Set exactly one scope:
  • Use method_id when the destination should be registered for one withdrawal method.
  • Use network_id when the destination should be valid for methods on that network.
Funding validates the scope and address fields. Collect the fields identified by address_fields in the withdrawal-method response. Do not add local address-validation rules. Supply exactly one of address_details.crypto or address_details.fiat. For fiat addresses, use address_details.fiat.account_number for the account_number field and address_details.fiat.iban for iban. The account-holder name comes from verified account information. For example, register a EUR bank destination using the method and routing fields returned by discovery:
If an address field’s allowed_values is non-empty, select a listed value. For example:
Use that choice when registering the bank address:
The identifiers and bank details are illustrative; supply your destination’s details. Funding attempts automatic activation during address registration when the method permits it. If activation remains required or is denied, the response is 403 with funding_method_activation_required or funding_method_activation_denied. The deposit activation endpoint does not activate withdrawal methods. The following helpers register a crypto address:
Successful creation returns 200:
The name must be unique for the account owner and is limited to 64 characters. The same crypto destination can be registered under different names. Each registration counts toward the applicable address limit. A create request can return these 409 codes: Address limits vary by account and scope. Reuse an existing address or contact support when the limit is reached.

Get, rename, or delete a funding address

Use the stable address_id in the member path:
  • GET /v1/accounts/{account_id}/funds/addresses/{address_id} returns the saved scope, name, and address details.
  • PATCH /v1/accounts/{account_id}/funds/addresses/{address_id} renames the address and returns 204. If the name is already in use for the account owner, it returns 409 withdrawal_address_already_exists.
  • DELETE /v1/accounts/{account_id}/funds/addresses/{address_id} deletes the address and returns 204.
Get, update, and delete return 404 when the address is absent or unavailable to the selected account. Update can return 403 when the caller is not allowed to change the address. Always enforce account-to-address ownership in your integration.

Step 4: quote the withdrawal fee before you display it

Quote the fee for the gross amount you intend to withdraw. Use a method id from Step 1 as method_id. Fiat and crypto quotes may return a fee_token; use it when present. Skip this step when you do not need to show a fee before submitting. amount is the gross amount, inclusive of the fee.
A successful quote returns 201:
net_amount is what reaches the destination, and for same-asset fees net_amount + fee equals gross_amount. Each request returns a new quote and moves no funds. When present, pass fee_token to Step 5 to reuse this quote’s fee terms. Treat the token as opaque and sensitive: do not parse or modify it, do not log it, and do not reuse it across users or accounts. The quote checks the method and the amount only. Funding still checks balance, method minimums, address compatibility, and Travel Rule requirements when you create the withdrawal.

Step 5: create the withdrawal

amount is the gross amount debited from the account, inclusive of the fee. For same-asset fees, net_amount + fee equals gross_amount. To reuse the fee terms from Step 4, send its fee_token and set scope.method_id to the id of the method you quoted. Fee tokens cannot be used with network scope. Omit fee_token to have Funding calculate the fee when the withdrawal is created. Funding recalculates the absolute fee from the token’s pinned terms and this request’s amount. Submit the same amount as the quote to reproduce the fee you displayed.
The token does not pin the amount. Submitting a different amount does not invalidate the token, but Funding then recalculates the fee for the amount you submit, so the fee charged differs from the fee you displayed. The request is still subject to the usual amount checks. Request a new quote whenever the amount changes.
If the initial request is rejected with 400 on fee_token, request a new quote before submitting again. If an earlier attempt had an unknown outcome, reconcile it first; a later validation error does not establish that earlier outcome.
For a registered fiat address, call the same helper with the saved address, its compatible scope, and the fiat asset:
Successful withdrawal creation returns 201. Acceptance does not confirm settlement:

Address-ownership verification

When the response is 409 withdrawal_verification_required, read verification_methods. Funding can return self_attestation, satoshi_test, or digital_signature. Complete self_attestation through POST /v1/users/{user_id}/travel-rule/verifications, then submit a new withdrawal request. Contact support when the response does not include a method that the endpoint supports.

Withdrawal webhook events

Register a webhook through POST /v1/webhooks and subscribe to the withdrawal lifecycle events your integration needs: All withdrawal lifecycle events contain type, transaction_id, user_id, and account_id. They can also contain amount and fee:
For funds.withdrawal_failed, amount and fee are values recorded for the request. They do not confirm that PWS kept the amount debited or ultimately charged the fee. Failed withdrawals may be reversed. Canceled withdrawals do not emit this event. Use Portfolio Transactions to reconcile cancellations and the final account state. Use Portfolio Transactions to reconcile the current state and contact support when you need help with a held or failed withdrawal. A withdrawal can emit a held event before it completes or fails. Treat each webhook as a notification. Deliveries can be repeated, so use the svix-id header as the deduplication key and return a 2xx response after accepting the event. These events describe normal funding withdrawals. They are distinct from conversion.withdrawal_* events, which describe the outbound funding leg of a conversion.

Reconcile the withdrawal

List GET /v1/accounts/{account_id}/portfolio/transactions?types=withdrawal. Match the transaction whose reference.kind is funding and whose reference.id equals the returned withdrawal_id. Portfolio is the canonical status and history surface. Subscribe to transaction.withdrawal for change notifications, then retrieve the transaction from Portfolio. See Portfolio transaction webhooks. If the create response was lost, you may not have a withdrawal_id for exact correlation. Inspect the account’s recent withdrawal transactions before retrying. Absence from a single read does not prove the request failed; do not submit with a new key while the original outcome remains unclear.

Withdrawal best practices

  1. Reuse saved addresses: List funding addresses before creating another destination.
  2. Use the exact scope: Prefer method_id when multiple methods exist on the same network.
  3. Quote the fee before you display it: Method-list fees are estimates. Quote the fee for the amount you intend to withdraw, then send that quote’s fee_token with the same amount.
  4. Reconcile unknown outcomes first: Generate a UUIDv4 idempotency key before creating a withdrawal and keep it for any retry. Check Portfolio first; Account Link eligibility can change between attempts.
  5. Protect routing fields: Preserve required tags and memos, and do not log complete destination details.

Common errors

API reference