Crypto-as-a-Service

Overview

You can build fiat withdrawals from your Go Account by submitting transaction details to BitGo. BitGo uses the data you pass to construct an unsigned transaction that wallet admins can approve. After video verification, a BitGo Bank & Trust operator signs the transaction with the user key. Then a different BitGo Bank & Trust operator downloads and signs the transaction in the BitGo Offline Vault Console (OVC). BitGo then uploads and broadcasts the transaction.

Fiat Withdrawal Methods

You can withdraw fiat from a Go Account to a bank account using either ACH or Wire transfers. The method BitGo uses depends on the destination bank account supported payment rails.

Feature ACH Withdrawal Wire Withdrawal
Use Case Low-to-medium value, recurring withdrawals, and settlements that can wait 1–2 business days. Large or urgent withdrawals, cross-border payouts, and client settlements.
Settlement Time 1–2 business days. Same day (if submitted before cutoff).
Bank Support US bank accounts only. US and international bank accounts.
Currency USD to USD only. Varies by currency and region.
Requirements BitGo-approved recipient bank. BitGo-approved destination bank. May require a reference/memo ID.

BitGo-Approved Bank Accounts

Fiat withdrawals to bank accounts require the use of BitGo-approved bank accounts. You can use the Add bank account endpoint to add a bank account.

When you add a bank account to an enterprise it goes through a screening process. BitGo approves most bank accounts within 48 hours and you can use them for fiat operations immediately after approval. You can see the status of the bank account by calling the Get bank account endpoint and checking the verificationState field.

Approval Requirements

To ensure BitGo approves your bank account, verify that you meet the following requirements:

Requirement Description
Valid Routing Number For US bank accounts, provide a valid 9-digit ABA routing number. BitGo validates routing numbers against the Federal Reserve directory. Invalid routing numbers result in rejection.
Owner Name Match The ownerName must match the name of your end user. For example, if your enterprise is "John Doe", the bank account owner should be "John Doe".
Account Type You must specify the accountType field as checking or saving. This field is required for both ACH and wire transactions to process correctly.

Prerequisites

Cookbook

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

Withdraw Fiat to Bank AccountOpen Cookbook

1. Add Bank Account

If you added a bank account to fund your Go Account and are using the same account for withdrawals you can skip this step.

Endpoint: Add bank account

export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export ENTERPRISE_ID="<CHILD_ENTERPRISE_ID>"

# Note: 021000021 is a valid sample routing number (JPMorgan Chase Bank)
curl -X POST \
  https://app.bitgo-test.com/api/v2/bankaccounts \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "type": "ach",
    "name": "JPMorgan Chase Bank",
    "accountNumber": "123456789012",
    "routingNumber": "021000021",
    "currency": "tfiatusd",
    "enterpriseId": "'"$ENTERPRISE_ID"'",
    "ownerName": "John Doe",
    "accountType": "checking",
    "description": "ACH deposits and withdrawals",
    "ownerAddressLine1": "123 Main Street",
    "ownerAddressLine2": "Suite 400",
    "ownerAddressCityLocality": "New York",
    "ownerAddressStateProvince": "NY",
    "ownerAddressPostalCode": "10001",
    "ownerAddressCountryCode": "US",
    "bankAddressLine1": "456 Financial Plaza",
    "bankAddressCityLocality": "New York",
    "bankAddressStateProvince": "NY",
    "bankAddressPostalCode": "10005",
    "bankAddressCountryCode": "US"
  }'

Step Result

The response includes the bank account details and the verificationState field indicating the current approval status.

{
  "id": "60749ab75f8c4500060f5a8b244dd0cb",
  "idHash": "c2f4cf5555a66d77",
  "currency": "tfiatusd",
  "token": "tusd",
  "name": "JPMorgan Chase Bank",
  "shortCountryCode": "US",
  "accountNumber": "****9012",
  "enterpriseId": "1032e75c451052000436831deb797af1",
  "ownerName": "John Doe",
  "verificationState": "pending",
  "createdAt": "2025-01-12T15:30:00.000Z",
  "type": "ach",
  "routingNumber": "021000021",
  "accountType": "checking",
  "description": "ACH deposits and withdrawals",
  "ownerAddressLine1": "123 Main Street",
  "ownerAddressLine2": "Suite 400",
  "ownerAddressCityLocality": "New York",
  "ownerAddressStateProvince": "NY",
  "ownerAddressPostalCode": "10001",
  "ownerAddressCountryCode": "US",
  "bankAddressLine1": "456 Financial Plaza",
  "bankAddressCityLocality": "New York",
  "bankAddressStateProvince": "NY",
  "bankAddressPostalCode": "10005",
  "bankAddressCountryCode": "US",
  "virtualDepositOnly": false,
  "fee": {
    "amount": "1600",
    "individualFees": [
      {
        "type": "static",
        "amount": "1600"
      }
    ]
  }
}

Verification States

BitGo returns one of the following verification states:

  • pending - BitGo is reviewing the bank account. BitGo approves most accounts within 48 hours.
  • approved - BitGo approved the bank account; it is ready for fiat operations.
  • rejected - BitGo rejected the bank account. Review the requirements and resubmit.
  • revise - Bank account requires corrections. Update the account and resubmit.
  • removed - BitGo removed the bank account from the enterprise.

2. Get idHash

Endpoint: List Bank Accounts

export ENTERPRISE_ID="<ENTERPRISE_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"

curl -X GET \
  https://app.bitgo-test.com/api/v2/bankaccounts?enterpriseId=$ENTERPRISE_ID \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Step Result

BitGo identifies your bank account with a unique idHash that you can pass as the address value in the next step. You also receive information about the available payment rails and associated bank fees.

{
    "bankAccounts": [
        {
            "type": "wire", // available payment rails vary by bank and region
            "accountNumber": "012345678",
            "address1": "test",
            "address2": "test",
            "enterpriseId": "1032e75c451052000436831deb797af1",
            "id": "60749ab75f8c4500060f5a8b244dd0cb",
            "name": "My Bank",
            "owner": {
                "name": "test",
                "address1": "test",
                "address2": "test"
            },
            "idHash": "c2f4cf5555a66d77",
            "routingNumber": "087654321",
            "shortCountryCode": "US",
            "verificationState": "approved",
            "fee": { // fees vary by payment rail and bank
              "amount": "string",
              "individualFees": [
                {
                  "type": "static",
                  "amount": "string"
                }
              ]
            },
            "feeInfo": {
              "coin": "fiatusd",
              "bank": "silvergate",
              "type": "static",
              "amount": "string"
            },
        }
    ]
}

3. Build Transaction

To withdraw assets from your Go Account to a bank account, you must build a transaction and send the assets.

Endpoint: Build a Transaction

export COIN="<FIAT_ASSET_ID>" # Fiat asset IDs in Go Accounts always start with "ofc" (such as ofctusdt)
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export AMOUNT="<AMOUNT_IN_BASE_UNITS>"
export ADDRESS="<ID_HASH>" # Use the `idHash`, returned in the prior step

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/tx/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "recipients": [
      {
        "amount": "'"$AMOUNT"'",
        "address": "'"$ADDRESS"'"
      }
    ]
}'

Step Result

{
  "payload": "{\"coin\":\"ofctusdt\",\"recipients\":[{\"address\":\"tb1q5mwcr2749nnm5c5d6rzx5h92l2u9ex5jzygzd39vfv9qerj5fmjqwlgy4t\",\"amount\":\"1000\"}],\"fromAccount\":\"62c5b1de8a0c5200071c9a603bdbadc5\",\"nonce\":\"d8e5d930-f827-42b1-ad85-40544dad1be6\",\"timestamp\":\"2025-06-25T17:45:56.394Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":false,\"isIntraJXTransfer\":false}",
  "feeInfo": { "feeString": "0" },
  "coin": "ofc",
  "token": "ofctusdt"
}

4. Authenticate Transaction

Use your Go Account passphrase to authenticate the transaction. To ensure your passphrase never travels over the Internet, you must use BitGo Express in external-signing mode or the JavaScript SDK. If you're using Express in external signing-mode, configure your Go Account passphrase as the WALLET_<walletId>_PASSPHRASE environment variable.

export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export WALLET_PASSPHRASE="<YOUR_GO_ACCOUNT_PASSPHRASE>"

curl -X POST \
  http://$BITGO_EXPRESS_HOST/api/v2/ofc/signPayload \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
   "walletId": "'"$WALLET_ID"'",
   "walletPassphrase": "'"$WALLET_PASSPHRASE"'",
   "payload": "{\"coin\":\"ofcusdt\",\"recipients\":[{\"address\":\"tb1q5mwcr2749nnm5c5d6rzx5h92l2u9ex5jzygzd39vfv9qerj5fmjqwlgy4t\",\"amount\":\"1000\"}],\"fromAccount\":\"67ab85b5190bf872e84da2b6e527d9f3\",\"nonce\":\"77d91a11-a7fa-4ad7-91e5-f054328717f9\",\"timestamp\":\"2025-06-26T15:02:06.924Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":false,\"isIntraJXTransfer\":false}"
}

Step Result

{
  "coin": "ofcusdt",
  "recipients": [
    {
      "address": "tb1q5mwcr2749nnm5c5d6rzx5h92l2u9ex5jzygzd39vfv9qerj5fmjqwlgy4t",
      "amount": "1000"
    }
  ],
  "fromAccount": "62c5b1de8a0c5200071c9a603bdbadc5",
  "nonce": "77d91a11-a7fa-4ad7-91e5-f054328717f9",
  "timestamp": "2025-06-26T15:02:06.924Z",
  "feeString": "0",
  "shortCircuitBlockchainTransfer": false,
  "isIntraJXTransfer": false,
  "payload": "{\"coin\":\"ofcusdt\",\"recipients\":[{\"address\":\"tb1q5mwcr2749nnm5c5d6rzx5h92l2u9ex5jzygzd39vfv9qerj5fmjqwlgy4t\",\"amount\":\"1\"}],\"fromAccount\":\"62c5b1de8a0c5200071c9a603bdbadc5\",\"nonce\":\"77d91a11-a7fa-4ad7-91e5-f054328717f9\",\"timestamp\":\"2025-06-26T15:02:06.924Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":false,\"isIntraJXTransfer\":false}",
  "signature": "20dda1e9558adb297f69020f94cbf4955fabec73478506347dbb5ac8e73b506fc908bbc48bde75b339b99f5de808ed34b2017055c9df198fd1f8b4e524899f9583"
}

5. Send Transaction

Endpoint: Send Half-Signed Transaction

export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export ADDRESS="<ID_HASH>"
export COIN="<FIAT_ASSET_ID>"
export AMOUNT="<AMOUNT_IN_BASE_UNITS>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/tx/send \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "recipients": [
      {
        "address": "'"$ADDRESS"'",
        "amount": "'"$AMOUNT"'"
      }
    ],
    "halfSigned": {
      "payload": {
        "coin": "ofctusd",
        "recipients": [
          {
            "address": "'"$ADDRESS"'",
            "amount": "'"$AMOUNT"'"
          }
        ],
        "fromAccount": "<WALLET_ID>",
        "nonce": "f4af25d0-a037-4afc-8ce6-d7f158d826d1",
        "timestamp": "2021-09-10T22:45:20.777Z",
        "idfSignedTimestamp": "2021-09-10T22:45:20.113Z",
        "idfVersion": 1,
        "idfUserId": "<USER_ID>",
        "feeString": "1000"
      },
      "signature": "<signature>"
    }
  }'

Step Result

{
    "transfer": {
        "id": "<id>",
        "coin": "ofctusd",
        "wallet": "<wallet id>",
        "walletType": "trading",
        "enterprise": "603ca88c44f82002560378421dc7a191",
        "txid": "ddd6875812fa59f899a68059ff303bfa6d39fc350ee687c407383d0444b43d2b",
        "height": 999999999,
        "heightId": "999999999-613be001f3de560006842d52251d5d49",
        "date": "2021-09-10T22:45:21.342Z",
        "type": "send",
        "value": -10000,
        "valueString": "-10000",
        "baseValue": -10000,
        "baseValueString": "-10000",
        "feeString": "0",
        "payGoFee": 0,
        "payGoFeeString": "0",
        "usd": -100,
        "usdRate": 1,
        "state": "signed",
        "instant": false,
        "isReward": false,
        "isFee": false,
        "tags": [
            "603ca8c01b83cb000656311787dab4cd",
            "603ca88c44f82002560378421dc7a191"
        ],
        "history": [
            {
                "date": "2021-09-10T22:45:21.340Z",
                "action": "signed"
            },
            {
                "date": "2021-09-10T22:45:21.216Z",
                "user": "5e4ed695ae2e542100336fb16e586019",
                "action": "created"
            }
        ],
        "signedDate": "2021-09-10T22:45:21.340Z",
        "entries": [
            {
                "address": "address",
                "wallet": "603ca8c01b83cb000656311787dab4cd",
                "value": -10000,
                "valueString": "-10000"
            },
            {
                "address": "bf6cc5252438dc85",
                "value": 10000,
                "valueString": "10000"
            }
        ],
        "signedTime": "2021-09-10T22:45:21.340Z",
        "createdTime": "2021-09-10T22:45:21.216Z"
    },
    "txid": "ddd6875812fa59f899a68059ff303bfa6d39fc350ee687c407383d0444b43d2b",
    "tx": {
    "transaction": {
      "coin": "ofctusd",
      "recipients": [
        {
          "address": "<bank id hash>",
          "amount": "10000"
        }
      ],
      "fromAccount": "<wallet id>",
      "nonce": "f4af25d0-a037-4afc-8ce6-d7f158d826d1",
      "timestamp": "2021-09-10T22:45:20.777Z",
      "idfSignedTimestamp": "2021-09-10T22:45:20.113Z",
      "idfVersion": 1,
      "idfUserId": "<user id>",
      "feeString": "0"
    },
    "signature": "<signature>",
    "idfVersion": 1,
    "idfSignedTimestamp": "2021-09-10T22:45:20.113Z",
    "idfUserId": "<user id>"
  },
    "status": "signed"
}

6. Confirm Fiat Withdrawal

As part of the security measure that ensures outgoing fiat transfers are intentional and that it is authorized by the requestor, you must confirm on the withdrawal request before BitGo can process them.

Confirmation API

Use the Confirmation API to programmatically approve or reject fiat withdrawals. This approach gives you full control over each withdrawal and allows you to implement custom approval workflows in your application.

Endpoint: Confirm fiat withdrawal

export WALLET_ID="<YOUR_WALLET_ID>"
export TRANSFER_ID="<YOUR_TRANSFER_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"

curl --request PUT \
  --url https://app.bitgo-test.com/api/v2/wallet/$WALLET_ID/fiatwithdrawals/$TRANSFER_ID/confirm \
  --header "accept: application/json" \
  --header "content-type: application/json" \
  --header "authorization: Bearer $ACCESS_TOKEN" \
  --data '{"action":"approve"}'

To reject a withdrawal instead, send {"action":"reject"}.

Automation

To automate confirmation, subscribe to the organization transfer webhook and call this endpoint when you receive a fiat withdrawal event. A fiat withdrawal is identified by walletType: "trading", transferType: "send", and subType: "ofc_withdrawal". See Webhooks.

Step Result

{
    "entries": [
        {
            "address": "6967b45a4ffdedb5747d5934e544b94c",
            "wallet": "6967b45a4ffdedb5747d5934e544b94c",
            "value": -2100,
            "valueString": "-2100",
            "isPayGo": false,
            "backingFeeString": "0",
            "token": "ofctusd"
        },
        {
            "address": "c7138929d032c83d",
            "value": 2000,
            "valueString": "2000",
            "isPayGo": false,
            "token": "ofctusd"
        }
    ],
    "id": "6980dfc5decaa85472a63ce93f0a07a7",
    "coin": "tfiatusd",
    "wallet": "6967b45a4ffdedb5747d5934e544b94c",
    "walletType": "trading",
    "enterprise": "6967afd69792fa80c629f22bb52caed6",
    "organization": "695d68b362ce13d38fa6a4fe2c7a6a82",
    "txid": "a10d7719179b5b6705b10a6bcd2cb492c10cae15564d567d4dd3e8e699718c64",
    "txidType": "transactionHash",
    "height": 999999999,
    "heightId": "999999999-6980dfc5decaa85472a63ce93f0a07a7",
    "date": "2026-02-02T17:33:05.285Z",
    "type": "send",
    "subType": "ofc_withdrawal",
    "value": -2100,
    "valueString": "-2100",
    "intendedValueString": "-2100",
    "baseValue": -2100,
    "baseValueString": "-2100",
    "baseValueWithoutFees": -2100,
    "baseValueWithoutFeesString": "-2100",
    "feeString": null,
    "payGoFee": 0,
    "payGoFeeString": "0",
    "usd": -21,
    "usdRate": 1,
    "medianUsdValue": -21,
    "medianUsdRate": 1,
    "feeUsd": 1,
    "feeUsdRate": 1,
    "state": "pendingApproval",
    "instant": false,
    "isReward": false,
    "isUnlock": false,
    "isFee": false,
    "senderInformationVerified": false,
    "tags": [
        "6967b45a4ffdedb5747d5934e544b94c",
        "6967afd69792fa80c629f22bb52caed6"
    ],
    "history": [
        {
            "date": "2026-02-02T17:33:05.285Z",
            "action": "unconfirmed",
            "comment": null
        },
        {
            "date": "2026-02-02T17:32:54.213Z",
            "action": "signed"
        },
        {
            "date": "2026-02-02T17:32:53.444Z",
            "user": "695d68b062ce13d38fa6a3ec72df6e4c",
            "action": "created"
        }
    ],
    "signedDate": "2026-02-02T17:32:54.213Z",
    "buildParams": {
        "recipients": [
            {
                "address": "c7138929d032c83d",
                "amount": "2000",
                "tokenName": "ofctusd"
            }
        ]
    },
    "metadata": [],
    "unconfirmedTime": "2026-02-02T17:33:05.285Z",
    "signedTime": "2026-02-02T17:32:54.213Z",
    "createdTime": "2026-02-02T17:32:53.444Z"
}

7. Approve Transaction (Optional)

Note

If you configure an approval requirement for withdrawals, you can't approve your own transactions - another admin must approve them.

Endpoint: Update Pending Approval

export COIN="<ASSET_ID>"
export APPROVAL_ID="<APPROVAL_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export OTP="<OTP>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/pendingapprovals/$APPROVAL_ID \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "state": "approved",
    "otp": "'"$OTP"'"
}'

Step Result

Once approved, BitGo rebuilds the unsigned transaction, applying the most up-to-date fees.

{
  "id": "655686880765186f0b3e9e88e1bdd0f4",
  "coin": "tbtc4",
  "wallet": "6553e933288be490293ae748efafeaaf",
  "enterprise": "62c5ae8174ac860007aff138a2d74df7",
  "creator": "62ab90e06dfda30007974f0a52a12995",
  "createDate": "2023-11-16T21:15:52.703Z",
  "info": {
    "type": "transactionRequest",
    "transactionRequest": {
      "requestedAmount": "10000",
      "fee": 45242,
      "sourceWallet": "6553e933288be490293ae748efafeaaf",
      "policyUniqueId": "6553e933288be490293ae753",
      "recipients": [
        {
          "address": "2N3sBpM1RnWRMXnEVoUWnM7xtYzL756JE2Q",
          "amount": "10000",
          "_id": "655686880765186f0b3e9e8a"
        }
      ],
      "buildParams": {
        "recipients": [
          {
            "address": "2N3sBpM1RnWRMXnEVoUWnM7xtYzL756JE2Q",
            "amount": "10000"
          }
        ]
      },
      "coinSpecific": {
        "tbtc4": {
          "txHex": "01000000000101eac5e7d68acfc2349672fb99094e79c2006e5fd663475c8f1eb6f29095970b740100000000ffffffff02102700000000000017a914747e6b7f5db53794b0fc01d95878e0f9b6db96738746c1020000000000220020efb9deaabeab5d62cecc363f24cd6deafcdd14d93d8734f7f01ed3953e732a640500483045022100cd6c221e4cefbb51aa82087d86e7d089b2473eb21ae809dba89eb9f13c4caf19022000f3b7f1b7fec2dc49cbfce35baa69ca37ab9ee8e51c779cde5b98a653f3e142010000695221029bde55661e4f359cf9ca2d1846c235e752f760ed6d65e2018f821253e78b3c722103f2b4a813ab79e4fb46edf3a6a87fb154a85b45810158e949f41a3fce3c9bf574210399a6d766f6d3a3843f8479446ee766b5745dc15c24d1db5149214d27987bd29453ae00000000"
        }
      },
      "validTransaction": "01000000000101eac5e7d68acfc2349672fb99094e79c2006e5fd663475c8f1eb6f29095970b740100000000ffffffff02102700000000000017a914747e6b7f5db53794b0fc01d95878e0f9b6db96738746c1020000000000220020efb9deaabeab5d62cecc363f24cd6deafcdd14d93d8734f7f01ed3953e732a640400483045022100cd6c221e4cefbb51aa82087d86e7d089b2473eb21ae809dba89eb9f13c4caf19022000f3b7f1b7fec2dc49cbfce35baa69ca37ab9ee8e51c779cde5b98a653f3e14201473044022002b246c43722fec49858caf6b0605a0d01a6b6c13c964b84bf272604fd3951780220324ee44ba3ce8febb154c317e688a5f6ed410ad8e2bd77c5678bf1f7f3eb45f601695221029bde55661e4f359cf9ca2d1846c235e752f760ed6d65e2018f821253e78b3c722103f2b4a813ab79e4fb46edf3a6a87fb154a85b45810158e949f41a3fce3c9bf574210399a6d766f6d3a3843f8479446ee766b5745dc15c24d1db5149214d27987bd29453ae00000000",
      "validTransactionHash": "ff40ccd5c8ba75ffdce27d8584b6e8ee625cb3f71f7cbb6d072a445a97c2c8c3"
    }
  },
  "state": "approved",
  "scope": "wallet",
  "userIds": [
    "62ab90e06dfda30007974f0a52a12995",
    "627ff9325a5c1b0007c05a40d15e1522"
  ],
  "approvalsRequired": 1,
  "singleRunResults": [
    {
      "ruleId": "Custody Enterprise Transaction ID Verification",
      "triggered": false,
      "_id": "655686880765186f0b3e9e89"
    }
  ],
  "resolvers": [
    {
      "user": "627ff9325a5c1b0007c05a40d15e1522",
      "date": "2023-11-16T21:33:24.644Z",
      "resolutionType": "pending"
    }
  ]
}

See Also