Send an Outbound Transfer
This guide outlines how to initiate a currency transfer out of a previously created user-controlled wallet. If you have not yet created a user-controlled wallet, go to this guide. If you do not have any tokens in your wallet, go to the inbound transfer guide.
The following steps utilize Circle’s sample applications in combination with API requests that can be done via Circle's API references or cURL requests. cURL request will be provided inline, while API references will be linked from the API endpoint code text. You can find instructions on using it in the testing via the reference pages guide.
1. Run Sample App
Once you have the iOS or Android sample app setup locally you will then
- Run the simulator
- Acquire an App ID from the developer console wallet configurator
- Add the App ID to the sample app
2. Acquire a Session Token
You will start by making a request to POST /users/token
using a previously created userId. The userToken
is a 60-minute session token to initiate requests requiring a user challenge (PIN code entry). After 60 minutes, the session expires, and a new userToken
must be generated via the same endpoint.
From this response, you will acquire the encryptionKey
and userToken
which you should provide in the respective sample app fields. Additionally, you will use the userToken
in Step 3.
curl --request POST \
--url 'https://api.circle.com/v1/w3s/users/token' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--data '
{
"userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463"
}
'
{
"data": {
"userToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCC9.eyJkZXZlbG9wZXJFbnRpdHlFbnZpcm9ubWVudCI6IlRFU1QiLCJlbnRpdHlJZCI6IjRlMDdhOGM5LTIxOTAtNDVlNC1hNjc0LWQyMGFkNjg4MWI3YyIsImV4cCI6MTY5MDU1MjcwNywiaWF0IjoxNjkwNTQ5MTA3LCJpbnRlcm5hbFVzZXJJZCI6ImQ2ZjkzODliLWQ5MzUtNWFlYy1iOTVhLWNjNTk1NjA2YWM5NiIsImlzcyI6Imh0dHBzOi8vcHJvZ3JhbW1hYmxlLXdhbGxldC5jaXJjbGUuY29tIiwianRpIjoiMmE0YmJlMzAtZTdkZi00YmM2LThiODMtNTk0NGUyMzE2ODlkIiwic3ViIjoiZXh0X3VzZXJfaWRfOSJ9.dhfByhxZFbJx0XWlzxneadT4RQWdnxLu3FSN9ln65hCDOfavaTL1sc4h-jUR8i4zMmfdURw3FFcQIdSbm-BUg6M7FP_fp-cs9xBbNmRZa31gMd1aKdcajJ9SvlVrfUowYfGXM3VcNF8rtTFtW-gk1-KzU4u10U35XXbbMcW1moxE0Rqx_fKotDgk2VdITuuds5d5TiQzAXECqeCOCtNoDKktMkglltbnLxOaRl2ReZjGt-ctD2V0DbYNO4T_ndPSUDI6qD7dXQRed5uDcezJYoha3Qj3tFGBglEnox2Y6DWTbllqjwmfTGrU8Pr0yz4jQz7suGwmiCzHPxcpYxMzYQ",
"encryptionKey": "Tlcyxz7Ts9ztRLQq5+pic0MIETblYimOo2d7idV/UFM="
}
}
3. Estimate gas
3.a Get the wallets ID
Make request to GET /wallets
passing in the wallets userToken
to get the walletId
.
curl --request GET \
--url 'https://api.circle.com/v1/w3s/wallets?pageSize=10' \
--header 'accept: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--header 'X-User-Token: <USER_TOKEN>'
{
"data": {
"wallets": [
{
"id": "01899cf2-d415-7052-a207-f9862157e546",
"state": "LIVE",
"walletSetId": "01899cf2-d407-7f89-b4d9-84d63573f138",
"custodyType": "ENDUSER",
"userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
"address": "0x075e62c80e55d024cfd8fd4e3d1184834461db57",
"addressIndex": 0,
"blockchain": "ETH-GOERLI",
"accountType": "EOA",
"updateDate": "2023-07-28T14:41:47Z",
"createDate": "2023-07-28T14:41:47Z"
}
]
}
}
3.b Check the wallet balance and acquire the token ID
Make a request to GET /wallet/{id}/balances
to check the balance of tokens and acquire the tokenId
you intend to transfer.
curl --request GET \
--url 'https://api.circle.com/v1/w3s/wallets/{id}/balances' \
--header 'accept: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--header 'X-User-Token: <USER_TOKEN>'
{
"data": {
"tokenBalances": [
{
"token": {
"id": "3495b830-d0f9-524b-89c0-4408274fed6e",
"blockchain": "ETH-GOERLI",
"tokenAddress": "",
"standard": "",
"name": "Ethereum-Goerli",
"symbol": "ETH-GOERLI",
"decimals": 18,
"isNative": true,
"updateDate": "2023-06-29T08:02:50Z",
"createDate": "2023-06-29T08:02:50Z"
},
"amount": "0.0788",
"updateDate": "2023-07-28T19:07:34Z"
}
]
}
}
3.c Estimate the cost of transferring the token
Make a request to POST transactions/transfer/estimateFee
to estimate the fees for transferring the token.
curl --request POST \
--url 'https://api.circle.com/v1/w3s/transactions/transfer/estimateFee' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--header 'X-User-Token: <USER_TOKEN>' \
--data '{
"amounts": [".0001"],
"destinationAddress": "0xEb9614D6d001391e22dDbbEA7571e9823A469c1f",
"tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",
"walletId": "01899cf2-d415-7052-a207-f9862157e546"
}
{
"data": {
"low": {
"gasLimit": "21000",
"baseFee": "2.456220277",
"priorityFee": "1.022783914",
"maxFee": "5.935224468"
},
"medium": {
"gasLimit": "21000",
"baseFee": "2.456220277",
"priorityFee": "2.655282857",
"maxFee": "7.567723411"
},
"high": {
"gasLimit": "21000",
"baseFee": "2.456220277",
"priorityFee": "15.986229693",
"maxFee": "20.898670247"
}
}
}
4. Initiate Blockchain Transfer
Make a request to POST /user/transactions/transfer
to initiate a blockchain transfer from a specifiedwalletId
to a specified blockchain addressdestinationAddress
. This call returns a challengeId
, which is used within the sample app to have the user enter their PIN code to authorize the transfer.
curl --request POST \
--url 'https://api.circle.com/v1/w3s/user/transactions/transfer' \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--header 'X-User-Token: <USER_TOKEN>' \
--data '{
"userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
"idempotencyKey": "607a0972-17f9-4d56-8ca3-a0e94adc3210",
"amounts": [".0001"],
"destinationAddress": "0x6E5eAf34c73D1CD0be4e24f923b97CF38e10d1f3",
"tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",
"walletId": "01899cf2-d415-7052-a207-f9862157e546",
"feeLevel": "MEDIUM"
}'
{
"data": {
"challengeId": "0d1b5f41-1381-50af-983b-f54691415158"
}
}
5. Authorize transfer from the sample app
Using the sample application, enter the userToken
and secretKey
returned from Step 2. Also, enter the challengeId
returned from Step 4.
At this point, you should be ready to execute your first transfer through the sample app. Click Execute in the sample app to continue.

The sample application takes you through the authentication process, which includes the user entering their PIN code to authorize the transfer.

6. Check the Transfer Status
As a transfer's state
changes and ultimately completes, Circle sends notifications to a subscribed endpoint. You can find a list of all possible states in the Asynchronous States and Statuses guide. The Webhook notification will be similar to the one below.
{
"subscriptionId": "d4c07d5f-f05f-4fe4-853d-4dd434806dfb",
"notificationId": "acab8c14-92ae-481a-8335-6eb5271da014",
"notificationType": "transactions.outbound",
"notification": {
"id": "720a46f2-35f8-55b0-845b-63fafca29580",
"blockchain": "ETH-GOERLI",
"walletId": "01899cf2-d415-7052-a207-f9862157e546",
"tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",
"userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
"destinationAddress": "0xeb9614d6d001391e22ddbbea7571e9823a469c1f",
"amounts": [
"0.0001"
],
"nftTokenIds": [],
"state": "CONFIRMED",
"errorReason": "",
"transactionType": "OUTBOUND",
"createDate": "2023-07-28T19:57:34Z",
"updateDate": "2023-07-28T19:57:50Z"
},
"timestamp": "2023-07-28T19:57:50.746029415Z",
"version": 2
}
Alternatively, you can poll GET /transactions
using the userId
or userToken
associated with your user.
curl --request GET \
--url 'https://api.circle.com/v1/w3s/transactions' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'authorization: Bearer <API_KEY>' \
--header 'X-User-Token: <USER_TOKEN>'
{
"data": {
"transactions": [
{
"id": "720a46f2-35f8-55b0-845b-63fafca29580",
"blockchain": "ETH-GOERLI",
"tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",
"walletId": "01899cf2-d415-7052-a207-f9862157e546",
"sourceAddress": "0x075e62c80e55d024cfd8fd4e3d1184834461db57",
"destinationAddress": "0xeb9614d6d001391e22ddbbea7571e9823a469c1f",
"transactionType": "OUTBOUND",
"custodyType": "ENDUSER",
"state": "CONFIRMED",
"amounts": [
"0.0001"
],
"nfts": null,
"txHash": "0x26e84f1e5b91b44946171de0133a42136e01ad113c389018e87afae706732430",
"blockHash": "0x64cca66b1eb813d69f89b6cbde0d88aca710b32aa8f41eaf7a5c820543ce49c4",
"blockHeight": 9424101,
"networkFee": "",
"firstConfirmDate": "2023-07-28T19:57:36Z",
"operation": "TRANSFER",
"userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
"abiParameters": null,
"createDate": "2023-07-28T19:57:34Z",
"updateDate": "2023-07-28T19:57:50Z"
},
{
"id": "97d22a88-6d25-5947-a7b6-61b3dc668057",
"blockchain": "ETH-GOERLI",
"tokenId": "3495b830-d0f9-524b-89c0-4408274fed6e",
"walletId": "01899cf2-d415-7052-a207-f9862157e546",
"sourceAddress": "0x6e5eaf34c73d1cd0be4e24f923b97cf38e10d1f3",
"destinationAddress": "0x075e62c80e55d024cfd8fd4e3d1184834461db57",
"transactionType": "INBOUND",
"custodyType": "ENDUSER",
"state": "COMPLETE",
"amounts": [
"0.0788"
],
"nfts": null,
"txHash": "0xdd2f81a78605dcad759265c703fb2b4c507c5ea100319338422714bfcde77225",
"blockHash": "0x4df6092fdb868331614771ff11944b43051cf6ed1067f8cfa55e9d40ef61426b",
"blockHeight": 9423950,
"networkFee": "0.000069299258595",
"firstConfirmDate": "2023-07-28T19:07:24Z",
"operation": "TRANSFER",
"userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
"abiParameters": null,
"createDate": "2023-07-28T19:07:34Z",
"updateDate": "2023-07-28T19:13:37Z"
}
]
}
}
Updated 2 days ago
Congratulations! You’ve received your first transaction to your user-controlled wallet. To learn how wallet recovery works, go to