Skip to main content
By the end of this guide, you’ll know how to add Mesh to your withdrawal flow using one of two integration paths: Mesh-hosted withdrawals (a complete withdrawal experience inside Mesh Link) or client-hosted withdrawals (you build the UI and use Mesh APIs for address retrieval and bridging).
Before you start
  • Your core Mesh integration is working end-to-end (see Prepare for go-live)
  • You’re on the Web SDK 3.14.1 or later.
    • Please contact your Mesh representative if you need Mesh-hosted withdrawals in the Mesh iOS, Android, React Native, or Flutter SDKs.
  • You have a withdrawal or payout flow in your app, and a custodian or wallet that sends funds from your platform
  • You’ve registered a webhook callback URI (see Transfer status webhooks)

Overview

Most withdrawal or payout flows require users to select an asset/network and then paste an address, just like they do in a deposit flow. And their primary concern is the same: losing funds because they make a mistake (ie. wrong network, wrong address, etc.). Mesh removes that risk, and lets users withdraw in the asset and network they want rather than only the ones you pay out in. There are two ways to integrate:
  • Mesh-hosted withdrawals: Mesh Link renders the full withdrawal flow and captures everything needed for the withdrawal. Mesh then delivers to you a payload with all the details, and you send the funds.
  • Client-hosted withdrawals: You keep your own withdrawal UI and call Mesh APIs to retrieve addresses from connected accounts and, optionally, to bridge to additional destinations.

Choose your integration path

Option 1: Mesh-hosted withdrawals

Mesh-hosted withdrawals is a complete withdrawal UX inside Mesh Link. You tell Mesh what the user can withdraw; the user chooses a destination, asset, network, and amount; and Mesh delivers a payload with everything you need to send the funds. Mesh never holds funds: your platform sends the payout, just as it does today.

Key benefits

  • Automated address retrieval: The user connects the exchange account or self-custody wallet they want to withdraw to, and Mesh retrieves the correct address for the selected asset and network. No copy/paste, no wrong-network mistakes. Users can also enter an address manually.
  • Bridging and swapping: Users can withdraw in the asset and network they want, not only the pairs you pay out in. When a destination needs a bridge or swap, Mesh gives you a single-use bridging address. You send your standard payout asset to it, and Mesh converts and delivers to the user’s destination automatically.
  • Wallet verification (coming soon): Confirm that your user controls the self-custody wallet they’re withdrawing to, without leaving the withdrawal flow.
  • Clear expectations: Before the user confirms, Link shows the amount they’ll receive after any applicable fees (and slippage for bridged routes).
  • A consistent round trip: The withdrawal experience looks and feels like the Mesh deposit flow your users already know, so the way out feels as familiar and trustworthy as the way in.

How it works

  1. Your backend requests a Link token with transferType: "withdrawal" and the asset/network pairs you can pay out from.
  2. You launch Mesh Link.
    • The user selects a destination, asset, network, and amount, then reviews and confirms.
    • Mesh fires the withdrawalRequested SDK event.
  3. You use the onEvent() callback to manage the user journey in your app, such as triggering 2FA if applicable.
  4. You receive the webhook event to your server with the money movement details of the requested withdrawal, including the address to send funds to.
  5. Your platform sends the funds. Mesh detects the transfer onchain, bridges or swaps if needed, and delivers to the user’s destination.
  6. Mesh sends a Succeeded webhook once the funds arrive.
Call the Link Token endpoint with the same credentials you use today. Add a transferOptions object with transferType: "withdrawal" and fromPairs (the asset/network pairs your platform pays out from, with the maximum amount the user can withdraw from each).
X-Client-Id required
Your Mesh Client ID.
X-Client-Secret required
Your Mesh API Key.
userId required
A unique, persistent user identifier. Personally identifiable information such as an email address or phone number should not be used. 300 characters length maximum.
transferOptions.transactionId optional
Recommended. Your own unique identifier for this withdrawal, up to 128 characters. Mesh returns it as TransactionId in webhooks so you can match events to your records.
transferOptions.transferType required
Set to withdrawal to launch the Mesh-hosted withdrawal flow.
transferOptions.fromPairs.networkId required

Mesh’s identifier for a network you pay out from.

Use the Get networks endpoint (/api/v1/transfers/managed/networks) to pull the list of supported networks and their networkId. These values don’t change, so you can cache them.

transferOptions.fromPairs.symbol required
The symbol of the asset you pay out on that network (eg. USDC).
transferOptions.fromPairs.maxAmount required

The maximum amount of symbol the user can withdraw from this pair, typically their withdrawable balance. Mesh enforces this limit in the user’s session.

This must be provided for every fromPairs entry.

Important note: Exclude amounts reserved for pending Mesh withdrawals.

If the user has no balance eligible for withdrawal, let them know why. Mesh will show a generic error screen when maxAmount is 0.00, but we recommend handling zero eligible balances as follows:
  • Balance that is not eligible to withdraw (eg. balance hasn’t met holding requirements, balance hasn’t met usage requirements, etc.): don’t provide a Withdraw option, and explain why.
  • True zero balance: don’t provide a Withdraw option in your UX.
Mesh’s minimum withdrawal amounts are $0.10 on a direct (ie. non-bridged) route, and $0.50 on a bridged route.
Launch Link with the token the same way you do for deposits (see Launch the Mesh SDK for SDK setup across platforms). Link then guides the user through:
  1. Destination: connect an exchange account or self-custody wallet, or enter an address manually.
  2. Asset and network: Link shows your fromPairs and any supported bridging routes from those fromPairs that are supported by the user’s chosen destination.
  3. Amount: The user can enter the amount they’d like to withdraw, up to their maxAmount.
  4. Review and confirm: Link shows the amount the user will receive after any applicable fees (and slippage for bridged routes).

3. Handle the withdrawalRequested SDK event (use if you require 2FA)

When the user confirms the withdrawal, Mesh fires the withdrawalRequested event and closes. You should record the withdrawal, and use the onEvent() callback to manage the user journey in your app (only after Link has closed): trigger 2FA or any other step your withdrawal flow requires, and show the user the right screen next (eg. a confirmation or a pending state). This event only tells you that the user requested a withdrawal. Its payload is transferId (Mesh’s identifier for the withdrawal) and status (pending or success; in practice pending, because you haven’t sent the funds yet). The money movement details (what to send, how much, and where) arrive separately, by webhook to your server (step 4).
Notes:
  • If the user fails or abandons 2FA, don’t send funds. The withdrawal ends as Failed once ExpiresAt passes, and no funds move.
  • If you don’t require 2FA, use this event to update your UI, then act on the webhook in step 4.
Never send funds based on the SDK event. Front-end data can be manipulated by the user, so withdrawalRequested doesn’t carry money movement details. Always take the send-to address, asset, network, and amount from the webhook in step 4, received and signature-verified on your server.
Let users resume an unfinished withdrawal. If a user dismisses or fails your 2FA step, the withdrawal stays pending until ExpiresAt. Users who can’t find a way back often start a new Mesh session and request the same withdrawal again. To prevent duplicates:
  • Show pending withdrawals in your app (eg. on your withdrawal or activity screen), with the amount, destination, and time remaining before ExpiresAt.
  • Add a “Finish verification” action that reopens your 2FA step for that withdrawal, using the transferId from withdrawalRequested.
  • Remove the item once you’ve sent the funds or ExpiresAt has passed.

4. Receive the withdrawal details by webhook

When the user completes the flow, Mesh sends a Pending transfer webhook to your registered callback URI with the money movement details of the requested withdrawal. This webhook, not the SDK event, is your source of truth for initiating the transfer. Verify the signature and respond exactly as described in Transfer status webhooks.
TransferType
Withdrawal for Mesh-hosted withdrawals.
TransactionId
The transactionId you passed in the Link token request, if any.
TransferId
Mesh’s unique identifier for this withdrawal.
SendToken, SendChain, SendNetworkId
The asset and network you must send. Always one of your fromPairs.
SendAmount
The exact amount of SendToken you must send.
SendToAddress
The address you send funds to. A single-use Mesh bridging address when IsBridged is true; otherwise the user’s destination address.
SendToAddressTag
Memo or destination tag to include with your send, when required. null otherwise.
ExpiresAt
Unix timestamp (seconds). Your send must be broadcast before this time. Starts when the user confirms in Link: 2 hours if not bridged, up to 2 hours if bridged. Always use this value; never assume 2 hours.
IsBridged
true when Mesh bridges or swaps your funds to reach the user’s chosen asset/network.
Token, Chain
The asset and network the user receives.
DestinationAddress, DestinationAddressTag
The user’s destination address, and address tag / memo if applicable.
DestinationProvider
Where the user is withdrawing to (eg. Phantom, Coinbase), or Manual for an entered address. Useful for display (eg. Your Phantom wallet (7xKX…gAsU)).
ExpectedDestinationAmount, MinDestinationAmount
The amount the user was shown, and the minimum they’ll receive after any applicable fees (and slippage for bridged routes).
Missed a webhook? Retrieve the withdrawal by TransactionId from the managed transfers endpoint (/api/v1/transfers/managed/mesh). It returns the same details as the webhook.

5. Send the funds

Use the webhook payload to initiate the transfer from your custodian or wallet: send exactly SendAmount of SendToken on SendChain to SendToAddress, including SendToAddressTag if present. Mesh detects the transfer onchain, and when IsBridged is true, bridges or swaps it and forwards it to the user’s DestinationAddress. Send before ExpiresAt. The deadline starts when the user confirms the withdrawal in Link:
  • Not bridged (same asset and network you pay out in): 2 hours.
  • Bridged: up to 2 hours. It can be shorter, because the bridge provider’s deposit address can expire sooner. Always use the ExpiresAt value in the payload; never assume 2 hours.
  • Mesh only sends the webhook while at least 1 minute remains. A retried webhook can arrive later, so compare ExpiresAt with your own clock before you send.
  • If your payout flow has extra steps (eg. 2FA or manual review), make sure they finish before ExpiresAt.
If you can’t send in time, don’t send. The withdrawal ends as Failed and you receive a Failed webhook. Ask the user to start a new withdrawal, which gives them a new quote and a new deadline.
Check the user’s balance before every payout. When you receive the withdrawal webhook, confirm that SendAmount doesn’t exceed the user’s remaining eligible balance in your system before you send funds. Mesh enforces maxAmount in the session and has checks that prevent duplicate withdrawal requests, but your balance check is the final safeguard against paying out more than the user is eligible to withdraw.
Send exactly what the payload specifies.
  • Wrong asset or network: sending any token other than SendToken, or on any network other than SendChain, to a bridging address isn’t processed and may result in lost funds.
  • After ExpiresAt: Mesh doesn’t track the payment and doesn’t return it automatically. If the withdrawal isn’t bridged, the funds still arrive at the user’s address, but the withdrawal shows Failed. If it’s bridged, contact Mesh support to recover the funds.
  • Different amount: the user receives an amount based on what you actually send, which won’t match what Link showed them.
  • Reuse: each bridging address is single-use. Never reuse it for a later withdrawal; request a new session instead.

6. Track delivery

Mesh monitors the transfer onchain and sends a Succeeded webhook once funds reach the user’s destination. For bridged withdrawals, TransferSteps lists each onchain leg with its TxHash, and DestinationAmount is what the user actually received.

Wallet verification (coming soon)

Wallet verification for Mesh-hosted withdrawals is coming soon. When a user withdraws to a self-custody wallet, you’ll be able to confirm that your user is in control of that destination wallet before any funds move. The user proves ownership by signing a message in their wallet as part of the same withdrawal flow: no gas fees and no extra steps outside Link.
  • Protect your users: reduce the risk of sending funds to a wallet your user doesn’t control.
  • Support your compliance program: capture proof of wallet ownership for Travel Rule and audit requirements.
  • No separate flow: verification happens inside the same Mesh-hosted session.
To verify self-custody wallets today, outside the withdrawal flow, see Verify self-hosted wallets.

Test your implementation

Mesh-hosted withdrawals can’t be tested in sandbox at this time.Test in production with small amounts (eg. 1 USDC), withdrawn to an address you control. Start with a route that isn’t bridged (the same asset and network you pay out in, eg. USDC on Base), then try a bridged route.
What to test:
  • Full flow: Launch a production withdrawal session with a small maxAmount, choose a destination you control, asset, network, and amount, and confirm.
  • 2FA hook: Confirm your onEvent handler receives withdrawalRequested and triggers your 2FA step.
  • Webhook capture: Confirm your server receives and verifies the Pending webhook, and that TransactionId matches your record (if you passed one).
  • Payout: Send SendAmount of SendToken to SendToAddress and confirm you receive the Succeeded webhook.
  • Abandon path: Close Link or fail 2FA before sending, and confirm no funds move and your app handles it gracefully.

Option 2: Client-hosted withdrawals

Keep your own withdrawal UI and use Mesh for automated address retrieval. After connecting an account, Mesh lets you pull addresses with clear asset/network mapping, eliminating human error and the risk of lost funds. Additionally, after a deposit, “refund addresses” are also available via Mesh SDK events and webhooks. Together, these can form the foundation of an “address book” where users save their preferred withdrawal destinations for future use.

1. Register for, and consume Mesh Transfer Status webhooks

Why?: Mesh provides real-time updates to transfer statuses which can be used to power several things for your app. But one piece (providing the RefundAddress for user transfers from external accounts) can be the underpinning of creating a seamless roundtrip journey.
  • See the Transfer status webhooks guide for more info.
  • Callback URIs can be added in the Mesh dashboard in Account > API keys > Webhooks.

2. Save the RefundAddress and surface it as a withdrawal destination

Why?: Creating a seamless roundtrip journey for the user builds trust. Users often want to withdraw funds back to the same source from where they deposited. This process makes that easy. It feels personalized.
  • The objects below are in Mesh transfer webhook events. Save this data, and surface it as a possible withdrawal destination in your UX (eg. Your Binance account (0x31…cF98)).
  • Important notes:
    • Take proper care to ensure you associate it with the correct user.
    • That RefundAddress is specific to the Chain & Token. Any transfer of that specific token on that chain to that address is safe, but sending any other token or on any other network to that address may result in lost funds.
      • See Concepts for more information about refundAddress

3. Add a Connect account option in the withdrawal flow

Why?: Users’ number-one fear when transferring crypto is making a mistake — wrong network, wrong address. With Mesh Link, that becomes impossible because it’s all automated. Providing an automated address retrieval option adds a layer of trust and certainty to the withdrawal process.
  • Call for a Link Token using the request structure below, and launch the Mesh Link SDK for the user.
X-Client-Id required
Your Mesh Client ID.
X-Client-Secret required
Your Mesh API Key.
userId required
A unique, persistent user identifier. Personally identifiable information such as an email address or phone number should not be used. 300 characters length maximum.
restrictMultipleAccounts optional

Defaults to false if omitted. Pass true for transfer-style Link (hides “Link another account”).

Set false if you want users to connect multiple accounts for an address book.

integrationId optional

A unique Mesh identifier representing a specific integration.

Use the Get integrations endpoint (/api/v1/transfers/managed/integrations) to pull a list of integrations and the corresponding Mesh integrationId. These values won’t change, so you do not have to hit this endpoint before each Link Token request.

To be used if the user selects the integration in your UX before launching Mesh (most commonly in an onramp flow).

  • The user will select an account, authenticate / provide access, and then their session will end.
  • You will receive an SDK event called integrationConnected. You should use the onIntegrationConnected() callback function to save the tokenId & brokerType values, associated with that userId.
  • Call the Mesh Get deposit addresses endpoint (/api/v1/transfers/managed/address/list), supplying the authToken (accessToken.accountTokens[].tokenId from integrationConnected) & type (accessToken.brokerType, e.g. coinbase, uphold, deFiWallet).
    • The response will return all addresses for each network on which the user can receive the specified crypto token. It will also have other helpful data to build you UX like a networkName for displaying the network and a logoUrl for that network.
    • Important note: Be sure to capture the memo for deposits of relevant assets (eg. XRP) into any custodial platform.
X-Client-Id required
Your Mesh Client ID.
X-Client-Secret required
Your Mesh API Key.
type required

The type of integration from which your pulling the user’s deposit addresses.

This is deFiWallet for self-hosted wallets, or a name of an exchange account (eg. uphold).

authToken required

The tokenId returned to you in the integrationConnected event when the user connected this account.

This provides access to read data from the account.

symbol required
The symbol of the asset for which you want the user’s deposit address(es) at the linked account.
networks.networkId optional

Mesh’s unique identifier for a specific network for which you would like to pull the user’s deposit address for symbol at the specified integration.

Use the Get networks endpoint (/api/v1/transfers/managed/networks) to pull a list of all supported networks and the corresponding Mesh networkId. These values won’t change, so you do not have to hit this endpoint before each Link Token request.

This should not be used with caipId. Use one or the other. If both are left blank, this endpoint will return addresses for all eligible networks for the specified symbol.

networks.caipId optional

A standardized identifier for blockchain networks/assets (e.g. eip155:1 is Ethereum mainnet).

This should not be used with networkId. Use one or the other. If both are left blank, this endpoint will return addresses for all eligible networks for the specified symbol.

4. Expand withdrawal destinations with bridging

Why?: The account-connection flow above returns the addresses a user natively holds. With Mesh headless bridging, you can offer additional withdrawal destinations you don’t natively settle in — Mesh bridges from an asset/network you do send from into the asset/network the user chooses, and returns an address for you to send to. It’s a pure API (no Mesh UI), so you keep your own withdrawal experience.
  • Call Discover withdrawal routes (POST /api/v1/bridging/routes/withdrawal) with the asset/network pairs your platform can pay out from (your settlement / omnibus legs). Mesh returns the bridged destinations it can reach from those sources. Mesh does not know which pairs the user already holds natively, so de-duplicate against the native addresses from the connection flow above before showing options.
  • When the user picks a native pair, send it as you do today — no extra Mesh call. When they pick a bridged pair, optionally call POST /api/v1/bridging/quote with the withdrawal amount to show the user an estimated receive amount, then call Create bridged address (POST /api/v1/bridging/address) with the selected route and the user’s destination address, and send your settlement funds to the returned bridging address before its constraints.expirationTime (6 hours). Mesh bridges them to the user’s chosen pair.
  • Each bridged transfer returns a Mesh transferId. Mesh sends a Succeeded or Failed webhook for it (no Pending), and you can poll GET /api/v1/transfers/managed/mesh by Id or ClientTransactionId at any point; the transfer is listed immediately as pending.
  • Important: Headless bridging is gated per-client, and its routes are configured on Mesh’s side. Contact your Mesh representative to enable it and to set up the routes for your settlement pairs.
  • See the full Headless bridging guide for request/response details, parameter tables, and status tracking.

What’s next

Explore the other Extend guides:
AI coding reference — a compact summary of this page’s APIs, parameters, and patterns for use by AI coding assistants (following the llms.txt standard). Human readers can safely ignore this.llms.txt — Add Mesh to your withdrawal flowTwo integration paths. Option 1, Mesh-hosted: Mesh Link renders the full withdrawal flow (destination, asset, network, amount, review); Mesh delivers the withdrawal details by webhook; client sends funds. Option 2, Client-hosted: client builds UI; uses Link to connect an account, the address list API, RefundAddress from webhooks, and optional headless bridging.Option 1 — Endpoint: POST /api/v1/linktoken (same credentials as deposits). Web SDK 3.14.1+.Option 1 — Required params: userId | transferOptions.transferType: "withdrawal" | transferOptions.fromPairs[] with networkId, symbol, maxAmount (pairs the client pays out from; maxAmount = user’s withdrawable balance). Recommended optional: transferOptions.transactionId (client’s own id for the withdrawal, up to 128 characters; echoed as TransactionId). If the user has no eligible balance (maxAmount would be 0), don’t offer Withdraw; Mesh only shows a generic error. Minimum withdrawal: $0.10 direct, $0.50 bridged.Option 1 — Coming soon: wallet verification (signed-message proof that the user controls a self-custody destination wallet) inside the hosted withdrawal flow. Not yet available; no parameters to pass today.Option 1 — SDK event: withdrawalRequested via onEvent() fires when the user confirms, then Link closes. Payload: transferId, status (pending | success; pending in practice). Use it to manage the user journey (eg. trigger 2FA, show a pending screen). It carries no money movement details. Never send funds from SDK event data; the server-side webhook is the source of truth for what to send, how much, and where.Option 1 — Webhook (Pending): TransferType: "Withdrawal" | TransactionId | TransferId | SendToken | SendChain | SendNetworkId | SendAmount | SendToAddress (single-use bridging address if IsBridged, else user’s address) | SendToAddressTag | ExpiresAt | IsBridged | Token / Chain (what the user receives) | DestinationAddress | DestinationAddressTag | DestinationProvider | ExpectedDestinationAmount | MinDestinationAmount. Shape is provisional.Option 1 — Payout rules: send exactly SendAmount of SendToken on SendChain to SendToAddress (with SendToAddressTag if present) before ExpiresAt. ExpiresAt starts at user confirmation: 2 hours if not bridged, up to 2 hours if bridged (always read the payload value). Webhook is sent only while at least 1 minute remains; retries can arrive later, so check ExpiresAt against your clock before sending. If you can’t send in time, don’t send: the withdrawal ends Failed (Failed webhook) and the user starts a new one. Late sends aren’t tracked or auto-returned: not bridged, funds reach the user but status is Failed; bridged, contact Mesh support. Wrong asset/network may lose funds. Never reuse a bridging address. Before every payout, validate SendAmount against the user’s remaining eligible balance in the client’s own system (final safeguard; Mesh also enforces maxAmount and guards against duplicate requests).Option 1 — Testing: not available in sandbox. Test in production with small amounts (eg. 1 USDC) to an address you control, starting with a non-bridged route.Option 1 — Completion: Succeeded webhook with TxHash, DestinationAmount, and TransferSteps[] per onchain leg. Fallback read: GET /api/v1/transfers/managed/mesh by TransactionId.Option 2 — RefundAddress from transfer webhooks: Save RefundAddress + Chain + Token + SourceAccountProvider per userId. Surface as a withdrawal destination (e.g. “Your Binance account (0x31…cF98)”). RefundAddress is specific to that chain+token.Option 2 — Withdrawal Link Token: POST /api/v1/linktoken with userId, optional restrictMultipleAccounts (defaults false when omitted; true hides “Link another account”, leave false to let users build an “address book” across multiple accounts), optional integrationId. No transferOptions. Receive integrationConnected → save accessToken.accountTokens[].tokenId + accessToken.brokerType → call address list.Option 2 — Deposit address retrieval: POST /api/v1/transfers/managed/address/list. Params: type (broker type, e.g. uphold, or deFiWallet for self-custody) | authToken (= accessToken.accountTokens[].tokenId) | symbol | networks[].networkId or networks[].caipId (optional; omit for all networks). Response includes address per network, networkName, logoUrl, memo (important for XRP, XLM).Option 2 — Headless bridging: To offer withdrawal destinations you don’t natively settle in, discover bridged destinations with POST /api/v1/bridging/routes/withdrawal (send your settlement pairs as sources; de-duplicate against the user’s native addresses, since Mesh does not know them), optionally quote the amount with POST /api/v1/bridging/quote, then when the user picks a bridged pair call POST /api/v1/bridging/address and send your settlement funds to the returned address before constraints.expirationTime (6h); track via the returned transferId (Succeeded/Failed webhooks, no Pending; list API shows it as pending immediately). Gated per-client. See the Headless bridging guide for full details.Get networkIds: GET /api/v1/transfers/managed/networks. Values don’t change; safe to cache.