Crypto-as-a-Service: Capture Client Fee
Capture client fee from completed trades through Go Account wallet transfer (Ta2Ta). See the Guide.
-
Subscribe to an organization-level trade webhook to receive real-time notifications when trades complete across your entire platform. This is a one-time setup. Your webhook receives two signals per completed trade: one at trade execution (the trigger for fee capture) and one when settlement completes (informational only). Act on the first signal where
statusis"completed"and nosettleDateis present. -
After receiving a trade completion webhook notification, retrieve the full order to get the fields needed for fee calculation:
filledQuantity,filledQuoteQuantity,averagePrice,product, andside. The webhook payload contains theaccountIdand orderid. You should check idempotency by tracking processed order identifiers to prevent duplicate fee captures from webhook retries. -
Build a wallet transfer for the calculated fee amount from the end-user's Go Account to your collection Go Account. This is an internal ledger movement (book transfer) with no on-chain transaction.
Important: You must disclose to your end users that you charge a fee on top of the trade execution price.
Calculate the fee based on trade direction:
- Buy (fiat to crypto):
fee = (filledQuoteQuantity * feeBps / 10000) / averagePrice-- denominated in the base token (e.g., BTC). SetCOINto the OFC-prefixed base asset (e.g.,ofctbtc4). - Sell (crypto to fiat):
fee = filledQuoteQuantity * feeBps / 10000-- denominated in the quote token (e.g., USD). SetCOINto the OFC-prefixed quote asset (e.g.,ofctusd).
- Buy (fiat to crypto):
-
Sign the transfer payload with the Go Account passphrase. Use BitGo Express in external-signing mode or the JavaScript SDK to ensure your passphrase is never sent over the internet. Initiate this transfer immediately upon trade completion -- delayed or batched processing risks the end-user withdrawing before you capture the fee.
-
Send the authenticated fee transfer to BitGo. If your enterprise has policy rules configured, the transfer enters a pending state until approved. Once confirmed, your collection Go Account receives the fee. You should display only the net balance (post-fee) to end-users and gate withdrawals until this transfer confirms to prevent a race condition.
// 1. Create Trade Webhook
export ORGANIZATION_ID="<YOUR_ORGANIZATION_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export URL="<YOUR_WEBHOOK_URL>"
export LABEL="<YOUR_WEBHOOK_NAME>"
curl -X POST \
https://app.bitgo-test.com/api/v2/organization/$ORGANIZATION_ID/webhook \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"type": "tradeOrder",
"url": "'"$URL"'",
"label": "'"$LABEL"'"
}'
// 2. Get Filled Order Details
export ACCOUNT_ID="<END_USER_GO_ACCOUNT_WALLET_ID>"
export ORDER_ID="<ORDER_ID_FROM_WEBHOOK>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
curl -X GET \
"https://app.bitgo-test.com/api/prime/trading/v1/accounts/$ACCOUNT_ID/orders/$ORDER_ID" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN"
// 3. Build Fee Transfer
export COIN="<OFC_ASSET_ID>"
export WALLET_ID="<END_USER_GO_ACCOUNT_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export FEE_AMOUNT="<CALCULATED_FEE_AMOUNT>"
export COLLECTION_WALLET_ID="<YOUR_COLLECTION_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": "'"$FEE_AMOUNT"'",
"walletId": "'"$COLLECTION_WALLET_ID"'"
}
]
}'
// 4. Authenticate Fee Transfer
export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
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": "<PAYLOAD_FROM_BUILD_STEP>"
}'
// 5. Send Fee Transfer
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_FROM_AUTH_STEP>"
}
}'
// 1. Create Trade Webhook
// Use the REST API to create a trade webhook (see cURL tab)
// 2. Get Filled Order Details
// Use the REST API to get the filled order (see cURL tab)
// 3. Build Fee Transfer
const BitGoJS = require('bitgo');
const bitgo = new BitGoJS.BitGo({
accessToken: process.env.ACCESS_TOKEN,
env: 'test',
});
async function captureFee() {
const coin = '<OFC_ASSET_ID>';
const walletId = '<END_USER_GO_ACCOUNT_WALLET_ID>';
const wallet = await bitgo.coin(coin).wallets().get({ id: walletId });
const transaction = await wallet.prebuildTransaction({
recipients: [
{
amount: '<CALCULATED_FEE_AMOUNT>',
walletId: '<YOUR_COLLECTION_GO_ACCOUNT_WALLET_ID>',
},
],
});
// 4. Authenticate Fee Transfer
const tradingAccount = wallet.toTradingAccount();
const signature = await tradingAccount.signPayload({
payload: JSON.stringify(transaction.payload),
walletPassphrase: process.env.WALLET_PASSPHRASE,
});
// 5. Send Fee Transfer
// Use the signature from step 4 with the tx/send endpoint (see cURL tab)
console.log('Payload:', transaction.payload);
console.log('Signature:', signature);
}
captureFee();
// 1. Create Trade Webhook Response
{
"success": "true",
"data": {
"id": "690278897328c7fc41e81887d4b76964",
"label": "Trade Completion Webhook",
"created": "2026-03-10T20:26:49.286Z",
"scope": "organization",
"organizationId": "68f6af3f2f29893d5863bc0eaf77b3c6",
"type": "tradeOrder",
"url": "https://your-platform.com/webhooks/trade",
"version": 1,
"state": "active"
}
}
// 2. Get Filled Order Details Response
{
"id": "67fd640c-cb6c-4218-80ae-49e79ec15646",
"accountId": "60e740e7898f7d00064d43769a73dc48",
"clientOrderId": "myorderid1",
"time": "2021-08-05T18:05:23.431Z",
"creationDate": "2021-08-05T18:05:22.286Z",
"scheduledDate": "2021-08-05T18:05:00.000Z",
"lastFillDate": "2021-08-05T18:05:23.302Z",
"completionDate": "2021-08-05T18:05:23.431Z",
"fundingType": "funded",
"type": "market",
"status": "completed",
"product": "TBTC-TUSD*",
"side": "buy",
"quantity": "1000",
"quantityCurrency": "TUSD*",
"filledQuantity": "0.02457152",
"filledQuoteQuantity": "1000",
"averagePrice": "40697.32"
}
// 3. Build Fee Transfer Response
{
"payload": "{\"coin\":\"ofctbtc4\",\"recipients\":[{\"address\":\"<COLLECTION_ADDRESS>\",\"amount\":\"<FEE_AMOUNT>\"}],\"fromAccount\":\"60e740e7898f7d00064d43769a73dc48\",\"nonce\":\"d8e5d930-f827-42b1-ad85-40544dad1be6\",\"timestamp\":\"2026-03-10T17:45:56.394Z\",\"feeString\":\"0\"}",
"feeInfo": { "feeString": "0" },
"coin": "ofc",
"token": "ofctbtc4"
}
// 4. Authenticate Fee Transfer Response
{
"coin": "ofctbtc4",
"payload": "{\"coin\":\"ofctbtc4\",...}",
"signature": "20dda1e9558adb297f69020f94cbf4955fabec73478506347dbb5ac8e73b506fc908bbc48bde75b339b99f5de808ed34b2017055c9df198fd1f8b4e524899f9583"
}
// 5. Send Fee Transfer Response
{
"transfer": {
"id": "613be001f3de560006842d52251d5d49",
"coin": "ofctbtc4",
"wallet": "60e740e7898f7d00064d43769a73dc48",
"type": "send",
"state": "signed"
},
"status": "signed"
}