Skip to main content
BVNK

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.

Prohibited jurisdictions

Self-custody wallets cannot be activated for customers in sanctioned jurisdictions.

To activate self-custody wallets, complete the following steps:

  1. Send the POST /platform/v2/agreements request with an Idempotency-Key header 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 to SELF_CUSTODY.
    • model: Set to EMBEDDED. 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 to RELIANCE by 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"
    }
  2. Send the GET /platform/v2/customers/{customerId}/agreements request 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.

  1. After the customer accepts the Self-Custody Agreement, send the POST /platform/v2/agreements/actions request with the ACCEPT action and an Idempotency-Key header. Pass the same reference you 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:

  1. Send the GET /ledger/v2/wallets/profiles request to find the id of a self-custody wallet profile to use as profileId.

    Identify the profile with an id in the format crypto:self-custodial:<id>, for example, crypto:self-custodial:16f910f5. Available profiles depend on your account and jurisdiction.

  2. Send the POST /ledger/v2/wallets request 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 INACTIVE and becomes ACTIVE once 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"
    }
  3. Wait for activation. After the wallet is activated, the paymentInstruments array will include the blockchain receive address. Use the wallet status change webhook to receive notifications when the wallet transitions to ACTIVE.

Why one wallet per network

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.

Example Response
{
"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.

Was this page helpful?