Set up self-custody wallets
Self-custody wallets let you provide customers with wallets to receive, hold, and send digital assets. Each customer has one master key, from which their wallet addresses are derived. You hold the key in a secure vault and can export it through the supported encrypted key-management flow.
Self-custody wallets are available through embedded integration. Your platform activates and creates wallets for customers and can initiate payment requests on their behalf.
Activate self-custody wallets
Self-custody wallets are only available via embedded integration.
Before creating self-custody wallets for customers, activate the feature for them. No compliance checks are required; customers must only accept BVNK's Self-Custody Agreement, confirming they understand they are responsible for their own assets.
Self-custody wallets are currently available only to customers created specifically for self-custody wallets, and these customers can hold only self-custody wallets. You cannot activate self-custody wallets for existing customers who use other BVNK products.
Self-custody wallets cannot be activated for customers in sanctioned jurisdictions.
To activate self-custody wallets, complete the following steps:
-
Send the
POST /platform/v2/agreementsrequest with anIdempotency-Keyheader to assign the Self-Custody Agreement to the customer. Remember to specify the following fields:reference: A unique identifier within your account. You reuse it when accepting the agreement.useCase: Set toSELF_CUSTODY.model: Set toEMBEDDED. You can omit it if your account is enabled exclusively for the embedded model. If the account is enabled for the partner-managed onboarding, omitting the value, will set it toRELIANCEby default.applicantEmail: The customer's email address.
Request{"reference": "3f8c2a71-5d4e-4b9a-8e1f-6c7d2b9a0e45","useCase": "SELF_CUSTODY","customerType": "INDIVIDUAL","countryCode": "US","model": "EMBEDDED","applicantEmail": "olivia.bennett@example.com"} -
Send the
GET /platform/v2/customers/{customerId}/agreementsrequest to retrieve the agreement assigned to the customer.Example Response{"totalElements": 1,"totalPages": 1,"content": [{"id": "59baa21b-06ea-4429-9c1c-e9a0179af484","agreement": {"version": null,"title": "Self-Custody Agreement","locale": null},"status": "PENDING","respondedAt": null,"respondedToDocumentChecksum": null,"createdAt": "2026-10-05T11:57:48.782607Z","updatedAt": "2026-10-05T11:57:48.782616Z"}],"pageable": {"pageNumber": 0,"pageSize": 64},"hasNext": false}
The agreement remains PENDING until the customer accepts it. Use its id as the agreementId in the next step.
-
After the customer accepts the Self-Custody Agreement, send the
POST /platform/v2/agreements/actionsrequest with theACCEPTaction and anIdempotency-Keyheader. Pass the samereferenceyou used in step 1.Request{"reference": "3f8c2a71-5d4e-4b9a-8e1f-6c7d2b9a0e45","actions": [{"agreementId": "59baa21b-06ea-4429-9c1c-e9a0179af484","type": "ACCEPT"}]}
When the agreement status is ACCEPTED, the customer is activated for self-custody wallets, and you can create a wallet.
Create a wallet
Self-custody wallets use the same endpoints as custodial wallets, but require two additional fields: network and profileId. Key management is handled through a dedicated set of endpoints under /ledger/v2/key-pairs.
To create a self-custody wallet, complete these steps:
-
Send the
GET /ledger/v2/wallets/profilesrequest to find theidof a self-custody wallet profile to use asprofileId.Identify the profile with an
idin the formatcrypto:self-custodial:<id>, for example,crypto:self-custodial:16f910f5. Available profiles depend on your account and jurisdiction. -
Send the
POST /ledger/v2/walletsrequest to create the wallet. Remember to specify the following fields:profileId: The self-custody wallet profile retrieved in step 1. Using a self-custody profile ensures the wallet is self-custodial.network: Each request creates one wallet for a single network. This field is mandatory.
The first self-custody wallet created for a customer automatically provisions their master key if it does not already exist. A key identifier is not required for this endpoint. If the customer wants to use their own mnemonic, import it before creating this wallet.
Request{"customerId": "d7344ac6-523b-49ef-9662-007433c8d526","currency": "USDC","name": "USDC Ethereum","network": "ETHEREUM","profileId": "crypto:self-custodial:16f910f5"}The response includes wallet details. The wallet is initially
INACTIVEand becomesACTIVEonce activation is complete:Example Response{"id": "a:26081030868292:Lu1Gywh:1","name": "USDC Ethereum","customer": {"id": "d7344ac6-523b-49ef-9662-007433c8d526","name": "Apex Trading Ltd"},"status": "INACTIVE","balance": {"amount": 0.000000,"currency": "USDC"},"paymentInstruments": [{"address": "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97","network": "ETHEREUM","tag": null}],"createdAt": "2026-09-10T14:05:12.830Z","updatedAt": "2026-09-10T14:05:12.830Z"} -
Wait for activation. After the wallet is activated, the
paymentInstrumentsarray will include the blockchain receive address. Use the wallet status change webhook to receive notifications when the wallet transitions toACTIVE.
Custodial wallets display one wallet per currency and manage network aliases internally. Self-custody addresses are derived according to BIP44 (purpose, coin_type), so an address is shared only across networks with the same coin type. For example, Ethereum, Arbitrum, Base, Polygon, and Binance share one address, while Solana and Tron use a unique address. The network field ensures each request creates a single wallet, and the response always returns one wallet object.
Supporting multiple networks
To support multiple networks, BVNK automatically creates a wallet when funds are received for a supported asset and network combination that does not yet have a wallet. You do not need to pre-create wallets for every network.
Get a wallet
To retrieve a self-custody wallet, use the same GET /ledger/v2/wallets/{id} request as for custodial wallets. The response structure is identical to wallet creation. Because self-custody maintains one wallet per currency and network, the response contains only that wallet and its paymentInstruments entry for the network address.
{
"id": "a:26081030868292:Lu1Gywh:1",
"name": "USDC Ethereum",
"customer": {
"id": "d7344ac6-523b-49ef-9662-007433c8d526",
"name": "Apex Trading Ltd"
},
"status": "ACTIVE",
"balance": {
"amount": 128.450000,
"currency": "USDC"
},
"paymentInstruments": [
{
"address": "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97",
"network": "ETHEREUM",
"tag": null
}
],
"createdAt": "2026-09-10T14:05:12.830Z",
"updatedAt": "2026-09-11T09:12:47.104Z"
}
To list all self-custody wallets a customer holds across networks, send a GET /ledger/v2/wallets request filtered by customerId.
Next steps
With self-custody wallets set up, you can manage the wallet lifecycle, secure customer keys, and initiate payments.
- Manage wallet status in the Set up wallets guide.
- Send payouts from a self-custody wallet.
- Protect key material and how it is encrypted in transit in the Security guide.
- Export a customer's key so they can manage or recover funds outside BVNK.
- List, import, and delete key pairs.
- View the Self-Custody Key Management API reference for detailed endpoint documentation.