Skip to main content

Prerequisites

  • Payward Services API credentials (see Authentication Guide)
  • A verified user with at least one account
Only cryptocurrency deposits are supported. Fiat deposits are not available through the Payward Services API.

Crypto deposits

Deposit workflow

1

List deposit methods

2
Query available deposit methods for the target asset. GET /v1/accounts/{account_id}/funds/deposits/methods/ {asset_symbol}
3

Create deposit address

Generate a new deposit address for the user. POST /v1/accounts/{account_id}/funds/deposits/addresses
4

Display address to user

Retrieve and display the deposit address in your UI. GET /v1/accounts/{account_id}/funds/deposits/addresses
5

User sends crypto

The user sends crypto from their external wallet to the deposit address.
6

Receive webhooks

Listen for deposit status updates. deposit.status_updated
Completed deposits will appear in GET /v1/accounts/{account_id}/portfolio/transactions?types=deposit.

Step 1: list deposit methods

Query available deposit methods for a crypto asset. Use the method’s id from the response (passed as method_id) when creating an address in Step 2.
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.
def list_deposit_methods(account_id, asset_symbol):
    endpoint = f"/v1/accounts/{account_id}/funds/deposits/methods/{asset_symbol}"

    signature = get_payward_signature(endpoint, None, API_SECRET)

    headers = {
        "API-Key": API_KEY,
        "API-Sign": signature,
    }

    response = requests.get(
        f"{BASE_URL}{endpoint}",
        headers=headers,
    )
    return response.json()


methods = list_deposit_methods(account_id, "BTC")
for m in methods["data"]:
    print(f"{m['network']} (id: {m['id']})")

Response example

{
  "data": [
    {
      "id": "b3a7d8b4-9f36-4c2e-9c1e-7b9e6f9a0a11",
      "network": "Bitcoin",
      "network_info": {
        "explorer": "https://blockstream.info/tx/{txid}",
        "confirmations": "3",
        "est_confirmation_time": "30 minutes",
        "contract_address": null
      },
      "fee": { "symbol": "BTC", "name": "Bitcoin", "type": "crypto", "amount": "0.00000000" },
      "fee_percentage": "0.00",
      "minimum": { "symbol": "BTC", "name": "Bitcoin", "type": "crypto", "amount": "0.00010000" },
      "maximum": { "symbol": "BTC", "name": "Bitcoin", "type": "crypto", "amount": "50.00000000" },
      "address_setup_fee": { "symbol": "BTC", "name": "Bitcoin", "type": "crypto", "amount": "0.00000000" }
    }
  ],
  "next_page_token": null
}
Key fields to display to users: network, fee, minimum, and network_info.est_confirmation_time. Pass the method’s id back as method_id when creating a deposit address.

Step 2: create a deposit address

Generate a deposit address using the method id from Step 1 as method_id. Display the address to the user so they can send crypto from an external wallet.
def create_deposit_address(account_id, asset_symbol, method_id, idempotency_key):
    endpoint = f"/v1/accounts/{account_id}/funds/deposits/addresses"

    body = {
        "asset_symbol": asset_symbol,
        "method_id": method_id,
    }

    signature = get_payward_signature(endpoint, body, API_SECRET)

    headers = {
        "API-Key": API_KEY,
        "API-Sign": signature,
        "Idempotency-Key": idempotency_key,
        "Content-Type": "application/json",
    }

    response = requests.post(
        f"{BASE_URL}{endpoint}",
        headers=headers,
        json=body,
    )
    return response.json()


address = create_deposit_address(
    account_id, "BTC", "b3a7d8b4-9f36-4c2e-9c1e-7b9e6f9a0a11", str(uuid.uuid4())
)
print(f"Deposit address: {address['data']['address']}")
{
  "data": {
    "address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
    "name": "Bitcoin",
    "tag": null,
    "memo": null,
    "expire_time": null,
    "is_new": true
  }
}
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.

Step 3: list deposit addresses

Retrieve existing deposit addresses for a given asset and method. Use this to display previously generated addresses to users without creating new ones each time.
def list_deposit_addresses(account_id, asset_symbol, method_id, cursor=None):
    endpoint = f"/v1/accounts/{account_id}/funds/deposits/addresses"

    params = {
        "asset_symbol": asset_symbol,
        "method_id": method_id,
    }
    if cursor:
        params["cursor"] = cursor

    signature = get_payward_signature(endpoint, None, API_SECRET, params)

    headers = {
        "API-Key": API_KEY,
        "API-Sign": signature,
    }

    response = requests.get(
        f"{BASE_URL}{endpoint}",
        headers=headers,
        params=params,
    )
    return response.json()


addresses = list_deposit_addresses(
    account_id, "BTC", "b3a7d8b4-9f36-4c2e-9c1e-7b9e6f9a0a11"
)
for addr in addresses["data"]:
    print(f"Address: {addr['address']}")

Response example

{
  "data": [
    {
      "asset": { "symbol": "BTC", "type": "crypto" },
      "method": {
        "id": "b3a7d8b4-9f36-4c2e-9c1e-7b9e6f9a0a11",
        "name": "Bitcoin"
      },
      "address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
      "tag": null,
      "memo": null,
      "expire_time": null
    }
  ],
  "next_page_token": null
}

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 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.

Crypto withdrawals

Withdrawals are key-based: you save an address once, then use its key in each withdrawal request.

Withdrawal workflow

1

List withdrawal methods

Query available withdrawal methods for the target asset. GET /v1/accounts/{account_id}/funds/withdrawals/methods/ {asset_symbol}
2

Validate address (optional)

Verify the destination address is valid before saving. POST /v1/funds/withdrawals/addresses/validate
3

Save address (create key)

Store the validated withdrawal address for the user. POST /v1/accounts/{account_id}/funds/withdrawals/addresses
4

Preview / submit withdrawal

Submit the withdrawal request. POST /v1/accounts/{account_id}/funds/withdrawals
5

Monitor status (webhook / polling)

Track the withdrawal via webhooks (withdrawal.status_updated) or transaction polling.

Step 1: list withdrawal methods

Call this first to determine the method’s id (passed as method_id in later requests), fee estimates, limits, and optional fee_token.
def list_withdrawal_methods(account_id, asset_symbol):
    endpoint = f"/v1/accounts/{account_id}/funds/withdrawals/methods/{asset_symbol}"

    signature = get_payward_signature(endpoint, None, API_SECRET)

    headers = {
        "API-Key": API_KEY,
        "API-Sign": signature,
    }

    response = requests.get(
        f"{BASE_URL}{endpoint}",
        headers=headers,
    )
    return response.json()

Response example

{
  "data": [
    {
      "id": "00e4796b-a142-4589-a7c1-8927933788c9",
      "network": "Bitcoin",
      "fee": { "symbol": "BTC", "name": "Bitcoin", "type": "crypto", "amount": "0.00020000" },
      "fee_token": "wft_abc123",
      "minimum": { "symbol": "BTC", "name": "Bitcoin", "type": "crypto", "amount": "0.00050000" },
      "maximum": { "symbol": "BTC", "name": "Bitcoin", "type": "crypto", "amount": "1.00000000" }
    }
  ],
  "next_page_token": null
}
This endpoint validates the destination before you save it.
def validate_withdrawal_address(asset_symbol, method_id, address, memo=None):
    endpoint = "/v1/funds/withdrawals/addresses/validate"

    body = {
        "asset_symbol": asset_symbol,
        "method_id": method_id,
        "address": address,
        "memo": memo,
    }
    signature = get_payward_signature(endpoint, body, API_SECRET)

    headers = {
        "API-Key": API_KEY,
        "API-Sign": signature,
        "Content-Type": "application/json",
    }

    response = requests.post(
        f"{BASE_URL}{endpoint}",
        headers=headers,
        json=body,
    )
    return response.json()

Step 3: save withdrawal address

Save the address once and keep the returned key for future withdrawals.
def save_withdrawal_address(account_id, asset_symbol, method_id, key, address, idempotency_key, memo=None, tag=None):
    endpoint = f"/v1/accounts/{account_id}/funds/withdrawals/addresses"

    body = {
        "asset_symbol": asset_symbol,
        "method_id": method_id,
        "key": key,
        "address": address,
        "memo": memo,
        "tag": tag,
    }
    signature = get_payward_signature(endpoint, body, API_SECRET)

    headers = {
        "API-Key": API_KEY,
        "API-Sign": signature,
        "Idempotency-Key": idempotency_key,
        "Content-Type": "application/json",
    }

    response = requests.post(
        f"{BASE_URL}{endpoint}",
        headers=headers,
        json=body,
    )
    return response.json()

Step 4: preview and submit a withdrawal

Use preview=true to quote fees and totals without creating a withdrawal, then submit with preview=false.
def withdraw_funds(account_id, asset_symbol, key, amount, idempotency_key, preview=False, fee_token=None):
    endpoint = f"/v1/accounts/{account_id}/funds/withdrawals"

    body = {
        "asset_symbol": asset_symbol,
        "key": key,
        "amount": amount,
        "preview": preview,
        "fee_token": fee_token,
    }
    signature = get_payward_signature(endpoint, body, API_SECRET)

    headers = {
        "API-Key": API_KEY,
        "API-Sign": signature,
        "Idempotency-Key": idempotency_key,
        "Content-Type": "application/json",
    }

    response = requests.post(
        f"{BASE_URL}{endpoint}",
        headers=headers,
        json=body,
    )
    return response.json()

Statuses

StatusDescription
pendingWithdrawal detected and being processed
heldHeld for review
successCompleted successfully
failureFailed (terminal)

Withdrawal best practices

  1. Use idempotency keys: Always generate a unique UUIDv4 and send it as the Idempotency-Key HTTP header per intended withdrawal to avoid duplicate sends on retries. Replayed responses include Idempotent-Replayed: true.
  2. Preview first: Run a preview request immediately before submit so users can confirm amount, fee, and total.
  3. Refresh expired fee tokens: fee_token values are short-lived. If a withdrawal is rejected due to an expired/invalid token, fetch withdrawal methods again (or run a fresh preview) to get a new fee_token and retry.
  4. Persist key ownership: Store which key belongs to each user and enforce access checks in your app.
  5. Handle memo/tag networks: For XRP/XLM-like networks, capture and persist memo/tag fields when addresses are saved.
  6. Monitor with webhooks: Subscribe to withdrawal.status_updated and reconcile events against your internal withdrawal records.

Common errors

HTTP statusCauseRemediation
400 Bad RequestInvalid payload (e.g. malformed key, amount, or unrecognized asset_symbol)Validate the request payload before sending
401 UnauthorizedMissing or invalid API credentialsVerify API-Key and API-Sign headers
404 Not FoundSaved withdrawal key does not existRe-list saved addresses and use an existing key
409 ConflictWithdrawal key already exists, or Idempotency-Key reused with a different payloadChoose a unique key, or reuse the same Idempotency-Key only for identical retries
429 Too Many RequestsRate limit exceededBack off and retry with exponential delay

API reference

EndpointMethodDescription
/v1/accounts/{account_id}/funds/deposits/methods/{asset_symbol}GETList deposit methods for an asset
/v1/accounts/{account_id}/funds/deposits/addressesPOSTCreate a new deposit address
/v1/accounts/{account_id}/funds/deposits/addressesGETList existing deposit addresses
/v1/accounts/{account_id}/funds/withdrawals/methods/{asset_symbol}GETList withdrawal methods for an asset
/v1/funds/withdrawals/addresses/validatePOSTValidate a withdrawal address without saving it
/v1/accounts/{account_id}/funds/withdrawals/addressesPOSTSave a withdrawal address
/v1/accounts/{account_id}/funds/withdrawals/addressesGETList saved withdrawal addresses
/v1/accounts/{account_id}/funds/withdrawals/addresses/{key}PATCHRename a saved withdrawal key
/v1/accounts/{account_id}/funds/withdrawals/addresses/{key}DELETEDelete a saved withdrawal address
/v1/accounts/{account_id}/funds/withdrawalsPOSTPreview or submit a withdrawal
/v1/funds/transactionsGETList funding (deposit and withdrawal) transactions
/v1/webhooksPOSTRegister for deposit.status_updated and withdrawal.status_updated webhooks