# Create Whitelists

Source: https://developers.bitgo.com/docs/custody-starter-architecture-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.

<Tabs>
<Tab title="Custody-Standby">

## Custody-Standby Whitelist

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

### Prerequisites

- [Get Started](/docs/get-started-intro)
- [Set Up Custody Wallet](/docs/custody-starter-architecture-custody-wallet)
- [Set Up Standby Wallet](/docs/custody-starter-architecture-standby-wallet)
- Custody Wallet ID and receive address.
- Standby Wallet ID and receive address.

### Cookbooks

Need just the steps? Expand a cookbook below to get started:

<Cookbook slug="custody-starter-architecture-create-whitelists-custody-standby" title="Create Whitelists: Custody ↔ Standby" />

<Cookbook slug="custody-starter-architecture-create-whitelists-standby-deposit" title="Create Whitelists: Standby ↔ Deposit/Withdraw" />

### 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](/reference/v2walletcreatepolicy)

```shell cURL
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-test.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"
  }
}'
```
```js JavaScript
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.

```json
{
  "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](/reference/v2walletcreatepolicy)

```shell cURL
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-test.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"
  }
}'
```
```js JavaScript
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

```json
{
  "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.

</Tab>
<Tab title="Standby-Deposit">

## Standby-Deposit Whitelist

Update the standby wallet whitelist to include the deposit/withdraw wallet address. This enables funds to flow between the standby wallet and the deposit/withdraw wallet.

After completing this section, the standby wallet can send funds to:

- The custody wallet (configured in the **Custody-Standby** tab).
- The deposit/withdraw wallet (configured in this section).

### Prerequisites

- [Get Started](/docs/get-started-intro)
- [Set Up Custody Wallet](/docs/custody-starter-architecture-custody-wallet)
- [Set Up Standby Wallet](/docs/custody-starter-architecture-standby-wallet)
- Custody-Standby Whitelist (see **Custody-Standby** tab).
- [Set Up Deposit/Withdraw Wallet](/docs/custody-starter-architecture-deposit-withdraw-wallet)
- Standby Wallet ID.
- Deposit/Withdraw Wallet receive address.

### Update Standby Wallet Whitelist

Add the deposit/withdraw wallet address to the standby wallet existing whitelist. This is an update to the whitelist policy you created earlier.

>Endpoint: [Update Wallet-Policy Rule](/reference/v2walletupdatepolicy)

```shell cURL
export COIN="<ASSET_ID>"
export STANDBY_WALLET_ID="<STANDBY_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export WHITELIST_RULE_ID="Standby to Custody Whitelist"
export DEPOSIT_WITHDRAW_ADDRESS="<DEPOSIT_WITHDRAW_WALLET_ADDRESS>"

curl -X PUT \
  "https://app.bitgo-test.com/api/v2/$COIN/wallet/$STANDBY_WALLET_ID/policy/rule" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "id": "'"$WHITELIST_RULE_ID"'",
  "type": "advancedWhitelist",
  "condition": {
    "add": {
      "type": "address",
      "item": "'"$DEPOSIT_WITHDRAW_ADDRESS"'",
      "metaData": {
        "label": "Deposit-Withdraw Wallet"
      }
    }
  },
  "action": {
    "type": "deny"
  }
}'
```
```js JavaScript
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 depositWithdrawAddress = '<DEPOSIT_WITHDRAW_WALLET_ADDRESS>';

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

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

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

  const policy = {
    action: {
      type: 'deny'
    },
    condition: {
      add: {
        item: depositWithdrawAddress,
        type: 'address',
        metaData: {
          label: 'Deposit-Withdraw Wallet'
        }
      }
    },
    id: 'Standby to Custody Whitelist', // Use existing rule ID to update
    type: 'advancedWhitelist'
  };

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

updateStandbyWhitelist();
```

#### Step Result

The standby wallet whitelist now includes both the custody wallet and the deposit/withdraw wallet addresses.

```json
{
  "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"
                }
              },
              {
                "item": "bc1p...",
                "type": "address",
                "metaData": {
                  "label": "Deposit-Withdraw Wallet"
                }
              }
            ]
          }
        }
      ]
    }
  }
}
```

>📘 **Note**
>
>For self-custody wallets, whitelist updates are subject to the same 48-hour lock period as new whitelists.

</Tab>
</Tabs>

## Verify Whitelists

Use the Get Wallet endpoint to verify whitelist policies.

>Endpoint: [Get Wallet by ID](/reference/v2walletget)

```shell cURL
export COIN="<ASSET_ID>"
export WALLET_ID="<WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"

curl -X GET \
  "https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```
```js JavaScript
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.

```json
{
  "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:

| Wallet | Can Send To |
| :----- | :---------- |
| **Custody Wallet** | Standby Wallet only |
| **Standby Wallet** | Custody Wallet, Deposit/Withdraw Wallet |
| **Deposit/Withdraw Wallet** | Any address (no whitelist) |

## Fund Flow Summary

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

```text
┌─────────────────────────────────────────────────────────────┐
│                    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](/docs/custody-starter-architecture-receive-addresses)

## See Also

- [Custody Starter Architecture Overview](/docs/custody-starter-architecture-overview)
- [Create Whitelists](/docs/wallets-whitelists-create)
- [Update Whitelists](/docs/wallets-whitelists-update)
