Create Whitelists

Overview

Whitelist policies restrict which addresses a wallet can send funds to. In the custody starter architecture, you create bidirectional whitelists between wallets so funds can only flow between designated wallets.

This guide covers creating whitelists for:

  • The custody wallet to allow withdrawals only to the standby wallet.
  • The standby wallet to allow withdrawals to the custody wallet and the deposit/withdraw wallet.

Custody-Standby Whitelist

Create bidirectional whitelists between the custody wallet and standby wallet so funds can only flow between these two wallets.

Prerequisites

Step 1: Create Whitelist on Custody Wallet

Add the standby wallet address to the custody wallet whitelist. This ensures funds from the custody wallet can only go to the standby wallet.

Endpoint: Add Wallet-Policy Rule

export COIN="<ASSET_ID>"
export CUSTODY_WALLET_ID="<CUSTODY_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export STANDBY_WALLET_ADDRESS="<STANDBY_WALLET_ADDRESS>"

curl -X POST \
  "https://app.bitgo.com/api/v2/$COIN/wallet/$CUSTODY_WALLET_ID/policy/rule" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "coin": "'"$COIN"'",
  "id": "Custody to Standby Whitelist",
  "type": "advancedWhitelist",
  "condition": {
    "add": {
      "type": "address",
      "item": "'"$STANDBY_WALLET_ADDRESS"'",
      "metaData": {
        "label": "Standby Wallet"
      }
    }
  },
  "action": {
    "type": "deny"
  }
}'
const BitGoJS = require('bitgo');
const bitgo = new BitGoJS.BitGo({ env: 'prod' });

const accessToken = '<YOUR_ACCESS_TOKEN>';
const coin = '<ASSET_ID>';
const custodyWalletId = '<CUSTODY_WALLET_ID>';
const standbyWalletAddress = '<STANDBY_WALLET_ADDRESS>';

async function createCustodyWhitelist() {
  bitgo.authenticateWithAccessToken({ accessToken });

  const wallet = await bitgo.coin(coin).wallets().get({ id: custodyWalletId });

  console.log(`Setting whitelist policy on custody wallet ${wallet.label()}`);

  const policy = {
    action: {
      type: 'deny'
    },
    condition: {
      add: {
        item: standbyWalletAddress,
        type: 'address',
        metaData: {
          label: 'Standby Wallet'
        }
      }
    },
    id: 'Custody to Standby Whitelist',
    type: 'advancedWhitelist'
  };

  const result = await wallet.createPolicyRule(policy);
  console.log('Whitelist created:', result);
}

createCustodyWhitelist();

Step Result

The whitelist policy is now active on the custody wallet. BitGo automatically denies any withdrawal attempt to an address not on the whitelist.

{
  "id": "66e9f4bb549f6c3957f8977fe3ed6ab4",
  "coin": "btc",
  "label": "Custody Wallet - Cold Storage",
  "admin": {
    "policy": {
      "rules": [
        {
          "id": "Custody to Standby Whitelist",
          "lockDate": "2025-09-18T22:05:21.219Z",
          "coin": "btc",
          "type": "advancedWhitelist",
          "action": { "type": "deny", "userIds": [] },
          "condition": {
            "entries": [
              {
                "item": "bc1p...",
                "type": "address",
                "metaData": {
                  "label": "Standby Wallet"
                }
              }
            ]
          }
        }
      ]
    }
  }
}
📘

Note

For self-custody wallets, new whitelist policies lock 48 hours after creation and can only be unlocked by BitGo support. Custody wallets have different lock periods managed by BitGo.

Step 2: Create Whitelist on Standby Wallet (to Custody)

Add the custody wallet address to the standby wallet whitelist. This allows funds to flow back from the standby wallet to the custody wallet.

Endpoint: Add Wallet-Policy Rule

export COIN="<ASSET_ID>"
export STANDBY_WALLET_ID="<STANDBY_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export CUSTODY_WALLET_ADDRESS="<CUSTODY_WALLET_ADDRESS>"

curl -X POST \
  "https://app.bitgo.com/api/v2/$COIN/wallet/$STANDBY_WALLET_ID/policy/rule" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "coin": "'"$COIN"'",
  "id": "Standby to Custody Whitelist",
  "type": "advancedWhitelist",
  "condition": {
    "add": {
      "type": "address",
      "item": "'"$CUSTODY_WALLET_ADDRESS"'",
      "metaData": {
        "label": "Custody Wallet"
      }
    }
  },
  "action": {
    "type": "deny"
  }
}'
const BitGoJS = require('bitgo');
const bitgo = new BitGoJS.BitGo({ env: 'prod' });

const accessToken = '<YOUR_ACCESS_TOKEN>';
const coin = '<ASSET_ID>';
const standbyWalletId = '<STANDBY_WALLET_ID>';
const custodyWalletAddress = '<CUSTODY_WALLET_ADDRESS>';

async function createStandbyToCustodyWhitelist() {
  bitgo.authenticateWithAccessToken({ accessToken });

  const wallet = await bitgo.coin(coin).wallets().get({ id: standbyWalletId });

  console.log(`Setting whitelist policy on standby wallet ${wallet.label()}`);

  const policy = {
    action: {
      type: 'deny'
    },
    condition: {
      add: {
        item: custodyWalletAddress,
        type: 'address',
        metaData: {
          label: 'Custody Wallet'
        }
      }
    },
    id: 'Standby to Custody Whitelist',
    type: 'advancedWhitelist'
  };

  const result = await wallet.createPolicyRule(policy);
  console.log('Whitelist created:', result);
}

createStandbyToCustodyWhitelist();

Step Result

{
  "id": "6849948ac0623f81f74f63dbd8351d4f",
  "coin": "btc",
  "label": "Standby Wallet - Hot",
  "admin": {
    "policy": {
      "rules": [
        {
          "id": "Standby to Custody Whitelist",
          "lockDate": "2025-09-18T22:05:21.219Z",
          "coin": "btc",
          "type": "advancedWhitelist",
          "action": { "type": "deny", "userIds": [] },
          "condition": {
            "entries": [
              {
                "item": "bc1q...",
                "type": "address",
                "metaData": {
                  "label": "Custody Wallet"
                }
              }
            ]
          }
        }
      ]
    }
  }
}
📘

Note

You add the deposit/withdraw wallet address to the standby wallet whitelist in the Standby-Deposit tab.

Verify Whitelists

Use the Get Wallet endpoint to verify whitelist policies.

Endpoint: Get Wallet by ID

export COIN="<ASSET_ID>"
export WALLET_ID="<WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"

curl -X GET \
  "https://app.bitgo.com/api/v2/$COIN/wallet/$WALLET_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const wallet = await bitgo.coin('<ASSET_ID>').wallets().get({ id: '<WALLET_ID>' });
console.log('Wallet policies:', wallet.admin().policy);

Step Result

Check the admin.policy.rules array in the response to confirm the whitelist entries.

{
  "id": "6849948ac0623f81f74f63dbd8351d4f",
  "coin": "btc",
  "label": "Standby Wallet - Hot",
  "admin": {
    "policy": {
      "rules": [
        {
          "id": "Standby to Custody Whitelist",
          "type": "advancedWhitelist",
          "condition": {
            "entries": [
              {
                "item": "bc1q...",
                "type": "address",
                "metaData": { "label": "Custody Wallet" }
              },
              {
                "item": "bc1p...",
                "type": "address",
                "metaData": { "label": "Deposit-Withdraw Wallet" }
              }
            ]
          }
        }
      ]
    }
  }
}

Whitelist Behavior

When you configure a whitelist policy with action.type: "deny":

  • Transactions to addresses on the whitelist proceed normally (subject to other policies).
  • Transactions to addresses not on the whitelist are automatically denied.

You can also configure whitelists with action.type: "getApproval" to require admin approval for non-whitelisted addresses instead of denying them outright.

Verify Complete Architecture

At this point, your custody starter architecture is fully configured. Verify the whitelist configuration:

WalletCan Send To
Custody WalletStandby Wallet only
Standby WalletCustody Wallet, Deposit/Withdraw Wallet
Deposit/Withdraw WalletAny address (no whitelist)

Fund Flow Summary

With the complete architecture in place, funds flow as follows:

┌─────────────────────────────────────────────────────────────┐
│                    FUND FLOW DIAGRAM                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  INBOUND (Deposits):                                        │
│  External ──────────────────────► Deposit/Withdraw Wallet   │
│                                                             │
│  INTERNAL (Accumulation):                                   │
│  Deposit/Withdraw ─────────────► Standby Wallet             │
│  Standby Wallet ───────────────► Custody Wallet             │
│                                                             │
│  INTERNAL (Replenishment):                                  │
│  Custody Wallet ───────────────► Standby Wallet             │
│  Standby Wallet ───────────────► Deposit/Withdraw Wallet    │
│                                                             │
│  OUTBOUND (Withdrawals):                                    │
│  Deposit/Withdraw Wallet ──────► External (Customers)       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Next

Create Receive Addresses

See Also