Crypto-as-a-Service
Set Up Fiat Reserve
Overview
BitGo requires you to pre-fund a fiat reserve to cover potential unauthorized return claims (fraud) from your end users. The reserve account uses the Go Account in your parent enterprise in the organization - not the child enterprises of your end users.
Your reserve account must meet the following requirements:
- Minimum balance: $25,000 if no deposit history.
- Standard balance: 25% of the highest 2-day deposit volume in the previous month.
- You must replenish the balance within 5 business days of dropping below the minimum balance.
Supported Payment Rails
| Jurisdiction | Currency | Payment Rails |
|---|---|---|
| United States | USD | ACH debit, domestic and international wire transfers, Go Network |
| Europe | EUR | SEPA Instant, SEPA, BLINC transfers, Go Network |
| Singapore | USD, SGD | FAST, International Wire |
Prerequisites
- Get Started
- Create Go Accounts
- Contact your BitGo Customer Success Manager (CSM) to designate an enterprise in your organization to be your parent enterprise.
Cookbooks
Need just the steps? Expand a cookbook below to get started:
Set Up Fiat Reserve - Bank TransferOpen Cookbook Set Up Fiat Reserve - Go Network TransferOpen CookbookFund Reserve Account
Deposit at least $25,000 into the Go Account of your parent enterprise using either a bank transfer or a Go Network transfer from another Go Account you control.
Bank Transfer
1. Get Deposit Info
Get deposit information for BitGo bank accounts. Available deposit mehtods vary by Go Account jurisdiction. EU residents have access to a Virtual International Bank Account Number (vIBAN).
Endpoint: Get deposit info
export CURRENCY="<FIAT_CURRENCY_ASSET_ID>"
export GO_ACCOUNT_ID="<GO_ACCOUNT_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
curl -X GET \
https://app.bitgo-test.com/api/v2/bankaccounts/deposit/info?currency=$CURRENCY&goAccountId=$GO_ACCOUNT_ID \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN"
export type Fee {
amount: string
individualFees:
{
type: "static" | "variable",
amount: string
}[]
}
export type BankAccount {
id: string;
description?: string;
idHash: string;
currency: string;
accountNumber: string;
token: string;
name: string;
shortCountryCode: string;
enterpriseId: string;
trustOrg: string;
ownerName: string;
verificationState: string;
createdAt: string;
ownerAddressLine1: string;
ownerAddressLine2?: string;
ownerAddressCityLocality?: string;
ownerAddressStateProvince?: string;
ownerAddressPostalCode?: string;
ownerAddressCountryCode: string;
bankAddressLine1: string;
bankAddressLine2?: string;
bankAddressCityLocality?: string;
bankAddressStateProvince?: string;
bankAddressPostalCode?: string;
bankAddressCountryCode: string;
virtualDepositOnly: boolean;
type: string;
routingNumber?: string;
swiftCode?: string;
accountType: string;
fee: Fee
}
export type DepositInfo = {
memoId: string;
bankAccounts: BankAccount[];
};
Step Result
Save the memoId if "virtualDepositOnly": false. BitGo uses this ID to link deposits to Go Accounts. If "virtualDepositOnly": true, then you have access to a vIBAN and a memoId isn't required.
{
"bankAccounts": [
{
"accountNumber": "123456789",
"accountType": "checking",
"address": "Ste 1400, Phoenix, AZ 85004, United States of America",
"address1": "Ste 1400, Phoenix, AZ 85004, United States of America",
"bankAddressCityLocality": "Phoenix",
"bankAddressCountryCode": "US",
"bankAddressLine1": "Ste 1400",
"bankAddressPostalCode": "85004",
"bankAddressStateProvince": "AZ",
"createdAt": "2026-07-22T19:39:01.484372Z",
"currency": "tfiatusd",
"enterpriseId": "68cc07a26b0eb871423ef15781d99121",
"fee": {
"amount": "1000",
"individualFees": [{ "amount": "1000", "type": "static" }]
},
"feeInfo": {
"amount": "1000",
"bank": "customers_cubix_api",
"coin": "tfiatusd",
"type": "static"
},
"id": "b23bbce8-dad1-41dd-ac71-4fd9bcd0a58b",
"idHash": "1d6fd97db569ff2b",
"name": "WESTERN ALLIANCE BANK",
"owner": {
"name": "BitGo Bank & Trust",
"address1": "2443 Ash Street, Suite 200, Palo Alto, CA 94306, United States of America"
},
"ownerAddress": "2443 Ash Street, Suite 200, Palo Alto, CA 94306, United States of America",
"ownerAddressCityLocality": "Palo Alto",
"ownerAddressCountryCode": "US",
"ownerAddressLine1": "2443 Ash Street",
"ownerAddressLine2": "Suite 200",
"ownerAddressPostalCode": "94306",
"ownerAddressStateProvince": "CA",
"ownerName": "BitGo Bank & Trust",
"routingNumber": "122105980",
"shortCountryCode": "US",
"token": "ofctusd",
"trustOrg": "BitGo Trust",
"type": "wire",
"verificationState": "approved",
"virtualDepositOnly": false
}
],
"memoId": "NKNKASPXVH"
}
2. Deposit Fiat Currency
Initiate the transfer from your bank app or API. Review the documentation provided by your bank or contact them to learn more.
Important
Ensure your deposit includes the memo ID returned in the previous step.
Step Result
The balance of fiat currency in your Go Account updates with the deposited amount. The deposited balance is immediately available for trades.
Go Network Transfer
1. Build Transaction
Build a transaction from the source Go Account to the destination Go Account. Pass the destination Go Account wallet ID as the value of the address field in the recipient object — the recipient object uses address, not walletId.
Endpoint: Build a Transaction
export COIN="<CRYPTO_ASSET_ID>" # Asset IDs in Go Accounts start with 'ofc' (e.g. ofctusd)
export WALLET_ID="<SOURCE_GO_ACCOUNT_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export AMOUNT="<AMOUNT_IN_BASE_UNITS>" # Must be $25,000 or more
export DESTINATION_WALLET_ID="<DESTINATION_GO_ACCOUNT_WALLET_ID>"
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": "'"$DESTINATION_WALLET_ID"'"
}
]
}'
let params = {
recipients: [
{
amount: 1000,
address: '654ec786c07fe6dc0dcfe03916ec4bb0',
},
],
};
wallet.prebuildTransaction(params).then(function (transaction) {
console.dir(transaction);
});
Step Result
The response payload contains shortCircuitBlockchainTransfer: true and isIntraJXTransfer: true, confirming that BitGo recognized this as an off-chain book transfer.
{
"payload": "{\"coin\":\"ofctusd\",\"recipients\":[{\"address\":\"2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY\",\"amount\":\"250000000\"}],\"fromAccount\":\"63ff43c474598902272894f328452ad7\",\"nonce\":\"80bc93b5-eeec-4c11-afca-f6dae4bba2f2\",\"timestamp\":\"2023-09-28T10:55:39.135Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":true,\"isIntraJXTransfer\":true}",
"feeInfo": { "feeString": "0" },
"coin": "ofc",
"token": "ofctusd"
}
2. Authenticate Transaction
Use your Go Account passphrase to authenticate the transaction. To ensure your passphrase isn't passed over the Internet, you must use BitGo Express in External-Signing Mode or the JavaScript SDK.
export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export WALLET_ID="<SOURCE_GO_ACCOUNT_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\":\"ofctusd\",\"recipients\":[{\"address\":\"2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY\",\"amount\":\"250000000\"}],\"fromAccount\":\"63ff43c474598902272894f328452ad7\",\"nonce\":\"80bc93b5-eeec-4c11-afca-f6dae4bba2f2\",\"timestamp\":\"2023-09-28T10:55:39.135Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":true,\"isIntraJXTransfer\":true}"
}'
const wallet = await bitgo.coin('ofc').wallets().get({ id: goAccountId });
if (wallet === undefined) {
throw new Error(`Could not find OFC wallet ${goAccountId}`);
}
const walletPassphrase = "<YOUR_WALLET_PASSPHRASE>";
const tradingAccount = wallet.toTradingAccount();
const stringifiedPayload = JSON.stringify(<payload_returned_by_tx/build_api>);
const signature = await tradingAccount.signPayload({
payload: stringifiedPayload,
walletPassphrase,
});
Step Result
{
"payload": "{\"coin\":\"ofctusd\",\"recipients\":[{\"address\":\"2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY\",\"amount\":\"250000000\"}],\"fromAccount\":\"63ff43c474598902272894f328452ad7\",\"nonce\":\"80bc93b5-eeec-4c11-afca-f6dae4bba2f2\",\"timestamp\":\"2023-09-28T10:55:39.135Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":true,\"isIntraJXTransfer\":true}",
"signature": "1f989926e3fc8ed0138bd0e188e90e2c5321ef86734896886c004005740a005dfd2008142dbc48aee68d1f576c4a9ac558e5b7b4acbd5aa765710ea636aca9d032"
}
3. Send Transaction
Using the payload and the signature from the prior step, send the half-signed transaction to BitGo.
Endpoint: Send Half-Signed Transaction
export COIN="<FIAT_CURRENCY_ASSET_ID>"
export WALLET_ID="<SOURCE_GO_ACCOUNT_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export PAYLOAD="<PAYLOAD_FROM_STEP_2>"
export SIGNATURE="<SIGNATURE_FROM_STEP_2>"
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 '{
"halfSigned": {
"payload": "'"$PAYLOAD"'",
"signature": "'"$SIGNATURE"'"
}
}'
// stringifiedPayload and signature come from Steps 1 and 2
const sendUrl = wallet.baseCoin.url('/wallet/' + wallet.id() + '/tx/send');
await bitgo
.post(sendUrl)
.send({ halfSigned: { payload: stringifiedPayload, signature } })
.result();
Step Result
The transactionType field confirms that BitGo processed this as a BOOK_TRANSFER.
{
"transfer": {
"id": "65155c4a72fddb000774edbee5fa75fd",
"coin": "ofctusd",
"wallet": "63ff43c474598902272894f328452ad7",
"walletType": "trading",
"enterprise": "63f31592e277e50837712300495902c7",
"txid": "ddd6875812fa59f899a68059ff303bfa6d39fc350ee687c407383d0444b43d2b",
"height": 999999999,
"heightId": "999999999-65155c4a72fddb000774edbe",
"date": "2023-09-28T10:58:18.272Z",
"type": "send",
"value": -250000000,
"valueString": "-250000000",
"baseValue": -250000000,
"baseValueString": "-250000000",
"feeString": "0",
"payGoFee": 0,
"payGoFeeString": "0",
"state": "signed",
"instant": false,
"isReward": false,
"isFee": false,
"tags": [
"63ff43c474598902272894f328452ad7",
"63f31592e277e50837712300495902c7"
],
"history": [
{
"date": "2023-09-28T10:58:18.340Z",
"action": "signed"
},
{
"date": "2023-09-28T10:58:18.272Z",
"user": "628ca09d1b78a6022750254f0777561a",
"action": "created"
}
],
"signedDate": "2023-09-28T10:58:18.340Z",
"entries": [
{
"address": "63ff43c474598902272894f328452ad7",
"wallet": "63ff43c474598902272894f328452ad7",
"value": -250000000,
"valueString": "-250000000"
},
{
"address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
"value": 250000000,
"valueString": "250000000"
}
],
"signedTime": "2023-09-28T10:58:18.340Z",
"createdTime": "2023-09-28T10:58:18.272Z"
},
"txid": "ddd6875812fa59f899a68059ff303bfa6d39fc350ee687c407383d0444b43d2b",
"tx": {
"transaction": {
"coin": "ofctusd",
"recipients": [
{
"address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
"amount": "250000000"
}
],
"fromAccount": "63ff43c474598902272894f328452ad7",
"nonce": "80bc93b5-eeec-4c11-afca-f6dae4bba2f2",
"timestamp": "2023-09-28T10:55:39.135Z",
"idfSignedTimestamp": "2023-09-28T10:55:38.732Z",
"idfVersion": 1,
"idfUserId": "628ca09d1b78a6022750254f0777561a",
"feeString": "0",
"shortCircuitBlockchainTransfer": true,
"isIntraJXTransfer": true
},
"signature": "1f989926e3fc8ed0138bd0e188e90e2c5321ef86734896886c004005740a005dfd2008142dbc48aee68d1f576c4a9ac558e5b7b4acbd5aa765710ea636aca9d032",
"transactionType": "BOOK_TRANSFER"
},
"status": "signed"
}
4. Approve Transaction (Optional)
Note
If you configure an approval requirement for transfers, you can't approve your own transactions — another admin must approve them.
Endpoint: Update Pending Approval
export APPROVAL_ID="<APPROVAL_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export OTP="<OTP>"
curl -X PUT \
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"'"
}'
const baseCoin = bitgo.coin(initialPendingApproval.coin);
const pendingApproval = await baseCoin.pendingApprovals().get({ id: initialPendingApproval.id });
const result = await pendingApproval.approve(params);
Step Result
Once approved, BitGo processes the book transfer and updates the ledger balances for both Go Accounts.
{
"id": "65155c4a72fddb000774edbee5fa75fd",
"coin": "ofctusd",
"wallet": "63ff43c474598902272894f328452ad7",
"enterprise": "63f31592e277e50837712300495902c7",
"creator": "628ca09d1b78a6022750254f0777561a",
"createDate": "2023-09-28T10:58:18.272Z",
"info": {
"type": "transactionRequest",
"transactionRequest": {
"coinSpecific": {
"ofctusd": {
"payload": {
"coin": "ofctusd",
"recipients": [
{
"address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
"amount": "250000000"
}
],
"fromAccount": "63ff43c474598902272894f328452ad7",
"nonce": "80bc93b5-eeec-4c11-afca-f6dae4bba2f2",
"timestamp": "2023-09-28T10:55:39.135Z",
"idfSignedTimestamp": "2023-09-28T10:55:38.732Z",
"idfVersion": 1,
"idfUserId": "628ca09d1b78a6022750254f0777561a",
"feeString": "0",
"shortCircuitBlockchainTransfer": true,
"isIntraJXTransfer": true
},
"signature": "1f989926e3fc8ed0138bd0e188e90e2c5321ef86734896886c004005740a005dfd2008142dbc48aee68d1f576c4a9ac558e5b7b4acbd5aa765710ea636aca9d032",
"transactionType": "BOOK_TRANSFER"
}
},
"verificationItems": ["2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY"],
"videoApprovers": [],
"requestedAmount": "250000000",
"fee": 0,
"sourceWallet": "63ff43c474598902272894f328452ad7",
"recipients": [
{
"_id": "65155c4a68fddb000865edbf",
"address": "2NDBpypweakRZzgbWqxEK42pnxgfYzd5iuY",
"amount": "250000000"
}
]
}
},
"state": "approved",
"scope": "wallet",
"userIds": [
"628ca09d1b78a6022750254f0777561a",
"5bd8bdb0925d7c9823d4fc823e9e2fd3"
],
"approvalsRequired": 1,
"resolvers": [
{
"user": "5bd8bdb0925d7c9823d4fc823e9e2fd3",
"date": "2023-09-28T11:15:24.644Z",
"resolutionType": "pending"
}
]
}
Next
If your ACH deposit volume grows, your fiat reserve must grow with it. Each month:
- Review your ACH deposit transaction history for the prior month.
- Identify the peak 2-day volume (the 48-hour window with the highest total USD inflow). For example, if you processed $1M in deposits over a Monday and Tuesday, then $1M is your peak 2-day volume.
- Calculate 25% of the peak amount. Using the prior example, that's $250,000.
- If 25% of your peak 2-day volume exceeds your current reserve balance, deposit the difference to bring the reserve up to the required level.
If the balance of your reserve account dips due to fraud or unauthorized returns, you must replenish it to the required level within 5 business days.
Warning
If your deposit volume increases but you don't maintain the reserve account to meet the 25% requirement, BitGo may suspend ACH access until you restore the reserve balance.
You can verify successful deposits by calling the Get Account Balance endpoint.