status is active
or verification_succeeded. To send funds, see
Send crypto transfers.
Your backend manages recipient addresses with your API key, for custodial and
non-custodial accounts alike. For a non-custodial account, register the address
for the same clientEntityId as the account. Registration doesn’t need the end
customer’s signature.
Prerequisites
Before you begin, ensure that you’ve:- Obtained an API key for Digital Asset Accounts from Circle.
- Onboarded the end customer and kept their
clientEntityId. See Onboard customers. - Run a device check for the end customer and kept the
deviceIdit returns. Every request that registers an address carries it inriskSignals. See Collect device risk signals. - For end customers in the European Economic Area (EEA), prepared a Strong Customer Authentication (SCA) assertion. Registering a recipient address is an SCA-gated operation. See Implement SCA.
- Installed cURL on your development machine.
Register a recipient address
Register the external blockchain address where you want to send stablecoins. For every field, seePOST /v1/addresses/recipient.
status is active or
verification_succeeded. If it isn’t yet, get it with
GET /v1/addresses/recipient/{recipientAddressId} until it is. For end
customers booked to Circle’s EEA entity, the request must also declare wallet
ownership, and you must send the X-Sca-Challenge-Id and X-Sca-Assertion
headers from an ADDRESS_BOOK_ADD challenge. For the ownership object, see
the example in Register a self-hosted wallet
and
EEA API behavior.
Register a self-hosted wallet
A self-hosted wallet is a blockchain wallet that an end customer controls directly, outside any custodian. Before you can send funds to one, the end customer must prove that they own it. Register the wallet with its ownership details, then have the end customer complete one of two ownership checks:- Message signature: The end customer signs a Circle-provided message with the wallet’s private key. Available for EVM blockchains and Solana.
- Test deposit: The end customer sends a small deposit, called a satoshi test, from the wallet. It confirms they control the private key. It’s the fallback for all other blockchains, and remains available on EVM blockchains and Solana.
Register the wallet with its ownership
CallPOST /v1/addresses/recipient with the wallet’s address and chain, the
end customer’s riskSignals, and an ownership object. For a self-hosted
wallet, set type to first_party, set custody.type to self_hosted, and
omit vaspId.
Self-hosted wallets support first-party ownership only. The wallet must belong
to the end customer; you can’t register a
third_party self-hosted wallet.X-Sca-Challenge-Id and X-Sca-Assertion) are required only
for end customers in the EEA. Omit them otherwise.
pending_verification and the response includes a
verificationChallenge. On EVM-family blockchains and Solana, the challenge
carries both a messageSignature block and a satoshiTest block, so you can
complete either check. On all other blockchains, only satoshiTest is present.
Verify ownership of the wallet
Complete one of the two checks. On EVM-family blockchains and Solana, the message-signature check and the satoshi test are available. On all other blockchains, use the satoshi test.Sign a message
Have the end customer sign theverificationChallenge.messageSignature.message
string exactly as returned, with the wallet’s private key:
- EVM-family blockchains: Produce a
personal_signsignature (EIP-191) and submit the0x-prefixed hex string. - Solana: Produce an
ed25519signature over the raw UTF-8 message bytes, with no hashing and no prefix, and submit the 64-byte signature as Base58 or Base64.
POST /v1/addresses/recipient/{recipientAddressId}/verification/signature:
200 response means the signature verified and the address moved out of
pending_verification. A 400 response means the signature was invalid or the
address was already verified.
If messageSignature.status becomes expired before you submit, request a
fresh challenge with
POST /v1/addresses/recipient/{recipientAddressId}/verification/resend, then
have the end customer sign the new message. A resend issues new challenges for
both methods.
Send a satoshi test
Have the end customer send exactlysatoshiTest.amount.amount from their
self-hosted wallet to satoshiTest.destinationAddress, in USDC for a USD
address or in EURC for an EUR address. If the amount or token doesn’t match,
the address stays pending_verification. If the chain requires a memo or tag,
include satoshiTest.paymentId.
If satoshiTest.status becomes expired before the deposit arrives, request a
fresh one with
POST /v1/addresses/recipient/{recipientAddressId}/verification/resend, then
repeat the deposit against the new satoshiTest.destinationAddress and
satoshiTest.amount values.
Poll until verification completes
Poll the address until itsstatus moves to verification_succeeded:
status changes to
verification_succeeded and the verificationChallenge is omitted:
verification_succeeded, it’s eligible for
transfers. A verified self-hosted wallet stays in this status. It doesn’t move
to active. See
Send crypto transfers.
List recipient addresses
Retrieve an end customer’s verified recipient addresses, which are eligible for transfers, withGET /v1/addresses/recipient.
This endpoint supports pagination. To check an address that is still being
verified, get it by id with
GET /v1/addresses/recipient/{id}.
Delete a recipient address
Remove a recipient address that is no longer needed, withDELETE /v1/addresses/recipient/{id}.
You can’t delete an address whose status is failed, disabled, or
verification_succeeded. The API returns a 400 error. Deleted addresses
cannot be used for new transfers.
200 response.
For an end customer in the EEA, also send the X-Sca-Challenge-Id and
X-Sca-Assertion headers from an ADDRESS_BOOK_DELETE challenge. See
Implement SCA.
Deleting a recipient address does not affect previously completed transfers to
that address. The transaction history is preserved.