Crypto-as-a-Service
Create Webhooks
Overview
You can use webhooks to receive real-time HTTP callbacks when specific events occur. This enables you to automate workflows, notify your end-users, and keep your platform in sync with BitGo.
As a CaaS provider, you operate at the Organization level and manage numerous child Enterprises (your end-users). This guide covers the two scopes of webhooks you can integrate:
- Organization Webhooks - Notifications for events that apply to your entire CaaS organization such as organization-level policy changes or new bank accounts.
- Enterprise Webhooks - Notifications for events specific to one of your end-users such as an end-user KYC status change or a new bank account added to an end-user enterprise.
BitGo signs all webhook notifications, enabling you to verify their authenticity.
- Webhook Secret - When you create a webhook, you can set a secret. BitGo uses this secret to generate an HMAC-SHA256 signature.
- Signature Header - BitGo sends the signature in the
x-signature-sha256HTTP header. - Verification - To verify the payload, compute your own HMAC-SHA256 signature of the raw request body using your secret and compare it to the signature in the header. This is a critical security step.
Note
You may receive multiple of the same webhook notification due to at-least-once delivery infrastructure. Always design your webhook handlers to be idempotent so you can identify duplicate notifications.
Enterprise Webhook Types
| Type | Triggers When |
|---|---|
accessToken |
A user creates an access token. |
bankAccount |
A user adds a bank account. |
reversal |
BitGo reverses a fiat transaction. |
Organization Webhook Types
| Type | Triggers When |
|---|---|
accessToken |
A user creates an access token. |
bankAccount |
A user adds a bank account. |
enterpriseKycState |
Enterprise KYC status changes. |
identityStatus |
A user identity status changes. |
idvStatus |
A user IDV status changes. |
pendingapproval |
A transaction request is pending approval. |
reversal |
BitGo reverses a fiat transaction. |
transfer |
A transfer succeeds or fails. |
txRequest |
A transaction request changes status. |
txRequestTransaction |
A transaction request transaction state changes. |
userKycState |
A user KYC verification status changes. |
tradeOrder |
A trade order status changes. |
Webhook Payload Schemas
Each webhook type returns a different JSON payload. Use these example schemas to parse and handle incoming webhook notifications.
Cookbooks
Need just the steps? Expand a cookbook below to get started:
Create Webhooks (Enterprise)Open Cookbook Create Webhooks (Organization)Open CookbookTriggered when a user creates an access token.
{
"userId": "59cd72485007a239fb00282a",
"username": "user@example.com",
"tokenCreateTime": "2024-01-15T10:30:00.000Z",
"tokenExpireTime": "2024-01-15T18:30:00.000Z",
"spendingLimits": "{\"txValue\":1000000,\"txValueLimit\":5000000}",
"tokenIp": "192.168.1.100",
"tokenIpWhitelist": ["192.168.1.0/24"],
"tokenScope": "wallet_view_all wallet_spend_all",
"enterpriseName": "Test Enterprise",
"id": "59cd72485007a239fb00282b",
"enterpriseId": "59cd72485007a239fb00282c",
"organizationId": "59cd72485007a239fb00282d"
}
Triggered when a user adds a bank account or its verification state changes.
{
"bankAccountId": "82156b5e-4e22-413f-8b53-8ce960f0b543",
"bankAccountName": "TestBank+2-Citi3-Operations",
"bankAccountType": "ach",
"enterpriseId": "6967d9efb19e363649a0a30559ae29ec",
"organizationId": "695d68b362ce13d38fa6a4fe2c7a6a82",
"idHash": "e4af999a758c5b74",
"accountNumber": "******4455",
"verificationState": "approved",
"type": "bankAccount"
}
Triggered when an enterprise KYC verification status changes.
Note
The kycState field can be approved, rejected, or pending.
{
"organizationId": "695d68b362ce13d38fa6a4fe2c7a6a82",
"kycState": "approved",
"type": "enterpriseKycState",
"id": "cmlqyh7x4001m0dix1rypheuq",
"enterpriseId": "6994b772b8f7aead2dd0e4c42d1181fe"
}
Triggered when a user identity verification status changes.
{
"id": "bf12d8e0-1207-4b5a-bb3d-b296a9caf9ef",
"organizationId": "695d68b362ce13d38fa6a4fe2c7a6a82",
"enterpriseId": "698f4b72460eb817cb415f8e6ba6565e",
"userId": "698f4b6f460eb817cb415e26ac1bdb71",
"identityId": "803b761a-7dfa-438d-8f92-b4270c136bd1",
"type": "identityStatus",
"status": "approved",
"signaturesSubmitted": [
"csa",
"mpa"
],
"signaturesRequired": []
}
Triggered when a user IDV (Identity Verification) status changes.
{
"type": "idvStatus",
"idvId": "98f20884-f3ce-4949-a969-4a6010ae00e1",
"status": "approved",
"verificationType": "penny-drop",
"userId": "6a048467a9198fefd14a12e39ffdf7a1",
"enterpriseId": "6a04846aa9198fefd14a143de0d6756f",
"organizationId": "69e291d44afc2b668a3ac76d16fef106",
"rejectLabels": null
}
Triggered when a transaction request requires approval or its approval state changes.
{
"type": "pendingapproval",
"pendingApprovalType": "transactionRequest",
"pendingApprovalId": "699727b1281a7931afe20b3549a9d93d",
"walletId": "6967dbd76e03e014ca3ccec6839a046a",
"walletLabel": "TestUser+3-ofc-wallet",
"state": "approved",
"simulation": false
}
Triggered when BitGo reverses a fiat transaction.
{
"type": "reversal",
"reversalType": "fiatTransaction",
"txId": "tx_123",
"enterpriseId": "ent_456",
"organizationId": "org_789",
"transactionType": "ach",
"state": "posted",
"reversesTxId": "tx_000",
"reversalReason": "insufficient_funds",
"rejectedReason": "ach_return_r01",
"timestamp": "2023-12-03T12:00:00.000Z"
}
Triggered when a transfer succeeds or fails.
{
"hash": "abbea60e9d0f9a2c480344f454b747e2e64ebcc96e3a819ccf87adddeb808e2b",
"transfer": "6995f3e4382a02d65d57e6ea60e8513d",
"coin": "ofctusd",
"type": "transfer",
"state": "confirmed",
"wallet": "6967dbd76e03e014ca3ccec6839a046a",
"walletType": "trading",
"transferType": "receive",
"baseValue": 10000,
"baseValueString": "10000",
"value": 10000,
"valueString": "10000",
"feeString": null,
"initiator": [
"external"
],
"systemNotes": {
"warning": "Do not rely solely on this webhook for final state. Always verify the current status via the API to ensure consistency. View the BitGo developer documentation and guides for more information."
}
}
A fiat withdrawal is identified by walletType: "trading", transferType: "send", and subType: "ofc_withdrawal". When you receive one, call the Confirm Fiat Withdrawal endpoint to approve or reject it.
Triggered when an on-chain transaction request changes status.
Note
Go Account to Go Account transfers are off-chain (BitGo internal ledger) and do not trigger this webhook.
{
"enterpriseId": "6967d9efb19e363649a0a30559ae29ec",
"organizationId": "695d68b362ce13d38fa6a4fe",
"walletId": "6994da8007e963313a01c6a77e28f94a",
"txRequestId": "c8450292-61f8-406d-bdc7-c1ca4bbb8147",
"txRequestVersion": 10,
"webhookType": "txRequest",
"sequenceIds": [],
"oldState": "pendingDelivery",
"newState": "delivered"
}
Triggered when a transaction request underlying transaction changes state.
{
"enterpriseId": "59cd72485007a239fb00282a",
"organizationId": "59cd72485007a239fb00282b",
"walletId": "59cd72485007a239fb00282c",
"txRequestId": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"txRequestVersion": 1,
"webhookType": "txRequestTransaction",
"transactionIndex": 0,
"sequenceId": "my-unique-sequence-id",
"oldState": "initialized",
"newState": "signed"
}
Triggered when a user KYC verification status changes.
Note
The kycState field can be approved, rejected, or pending.
{
"organizationId": "695d68b362ce13d38fa6a4fe2c7a6a82",
"kycState": "approved",
"type": "userKycState",
"id": "cmlqz57ct001j0esb6vde1qgp",
"userId": "6994bbd53763ba8c4e74ff38766c0ba8"
}
Triggered when a trade order status changes.
Note
The status field can be pending_open, open, completed with null settleDate, or completed with populated settleDate.
{
"id": "8c99dfb2-bd3e-455e-8bbd-b5b442d65e8e",
"accountId": "69979f2926b13520a108df8fe5deb360",
"enterpriseId": "69979ce2613a65a7a666c35abcc94855",
"initiatedByUserId": "654e94156e168842aa99bd51ffe3f9f8",
"canceledByUserId": null,
"clientOrderId": null,
"time": "2026-02-26T14:02:32.129212Z",
"creationDate": "2026-02-26T14:02:30.567219Z",
"scheduledDate": null,
"lastFillDate": "2026-02-26T14:02:31.824515Z",
"completionDate": "2026-02-26T14:02:32.129212Z",
"settleDate": "2026-02-26T14:03:23.868Z",
"fundingType": "funded",
"type": "market",
"timeInForce": null,
"status": "completed",
"reason": "",
"product": "TSOL-TUSD*",
"side": "buy",
"quantity": "6",
"quantityCurrency": "TUSD*",
"filledQuantity": "0.06864465",
"filledQuoteQuantity": "6",
"leavesQuantity": null,
"leavesQuoteQuantity": "0",
"averagePrice": "87.41",
"limitPrice": null,
"duration": null,
"twapInterval": null,
"rtId": null,
"notes": null,
"isFinanced": false
}
Prerequisites
1. Create Organization Webhooks
The following example creates a webhook that notifies you whenever a user adds a bank account.
Endpoint: Create Organization 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": "bankAccount",
"url": "'"$URL"'",
"label": "'"$LABEL"'"
}'
Step Result
{
"id": "690278897328c7fc41e81887d4b76964",
"label": "test org webhook 1",
"created": "2025-10-29T20:26:49.286Z",
"scope": "organization",
"organizationId": "68f6af3f2f29893d5863bc0eaf77b3c6",
"type": "bankAccount",
"url": "https://webhook.site/2635918b-018b-4179-82e9-6940a0b851e0",
"version": 1,
"state": "active",
"successiveFailedAttempts": 0,
"listenToFailureStates": false,
"txRequestStates": [],
"txRequestTransactionStates": [],
"identityStatus": []
}
2. (Optional) Create Webhook Secret
Create a webhook secret that enables you to verify webhook notifications. BitGo uses this secret to generate HMAC-SHA256 signatures for all webhook payloads, enabling you to verify that notifications genuinely originate from BitGo and that no one has tampered with them.
Important
Store your webhook secret securely. If compromised, rotate it immediately by calling this endpoint again. You can rotate secrets once every 5 minutes.
Note
You must create your webhook secret before performing the action that triggers the notification. Otherwise, the notification won't include a signature and you won't be able to verify it.
Endpoint: Create Webhook Secret
export ORGANIZATION_ID="<YOUR_ORGANIZATION_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
curl -X POST \
https://app.bitgo-test.com/api/v2/webhook/secret \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"organizationId": "'"$ORGANIZATION_ID"'"
}'
Step Result
{
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"
}
Save this secret securely—you'll need it to verify incoming webhook notifications using the x-signature-sha256 header.
3. (Optional) Verify Webhook Notification
When BitGo sends a webhook notification, it includes an x-signature-sha256 header containing an HMAC-SHA256 signature. You can verify the notification is legitimate by passing the signature from this header along with the payload you received.
Endpoint: Verify Webhook Notification
export WEBHOOK_ID="<YOUR_WEBHOOK_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export SIGNATURE="<X_SIGNATURE_SHA256_HEADER_VALUE>" # Value from the x-signature-sha256 header in the webhook notification
export PAYLOAD="<YOUR_PAYLOAD>" # JSON payload as a string from the webhook notification body
curl -X POST "https://app.bitgo-test.com/api/v2/webhook/$WEBHOOK_ID/verify" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"signature": "'"$SIGNATURE"'",
"notificationPayload": "'"$PAYLOAD"'"
}'
Step Result
{
"webhookId": "wh119ecd15a4adf811f8f552fde21b9d819b4dc9a7f04c51513395816703c73511",
"isValid": true
}
4. (Optional) Simulate Organization Webhook
You can simulate your webhook with real data from the prior step or with placeholder data (also known as dummy data).
Endpoint: Simulate Organization Webhook
export ORGANIZATION_ID="<YOUR_ORGANIZATION_ID>"
export WEBHOOK_ID="<YOUR_WEBHOOK_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export ACCESS_TOKEN_ID="<YOUR_ACCESS_TOKEN_ID>"
curl -X POST "https://app.bitgo-test.com/api/v2/organization/$ORGANIZATION_ID/webhook/$WEBHOOK_ID/simulate" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN"
-d '{
"bankAccountId": "string"
}'
Step Result
{
"webhookNotifications": [
{
"id": "59cd72485007a239fb00282ed480da1f",
"accessToken": "txRequest",
"url": "https://webhook.site/f74addc1-c40a-4fce-879a-2d92b8d491c5",
"hash": "db924f4cf2347ac5a6b464d3e8dc4a20cffc117eb29f4460645fbc34de171bfb",
"simulation": true,
"retries": 0,
"webhook": "68531e154d28af627819bd3e183a930e",
"updatedAt": "2025-06-19T20:48:43.525Z",
"version": 1,
"allowBlockedHosts": true,
"payload": "string",
"response": {
"code": 0,
"type": "string",
"body": "string",
"error": "string"
}
}
]
}
1. Create Enterprise Webhooks
The following example creates a webhook that notifies you whenever a user creates an access token.
Endpoint: Create Enterprise Webhook
export ENTERPRISE_ID="<YOUR_ENTERPRISE_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/enterprise/$ENTERPRISE_ID/webhooks \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"type": "accessToken",
"url": "'"$URL"'",
"label": "'"$LABEL"'"
}'
Step Result
{
"id": "6854341f9b1bb1a97f4ad2d8af07f178",
"label": "my-enterprise-access-token-webhook",
"created": "2025-06-19T16:00:31.738Z",
"scope": "enterprise",
"enterpriseId": "62c5ae8174ac860007aff138a2d74df7",
"type": "accessToken",
"url": "https://webhook.site/f74addc1-c40a-4fce-879a-2d92b8d491c5",
"version": 2,
"state": "active",
"successiveFailedAttempts": 0,
"listenToFailureStates": false,
"txRequestStates": [],
"txRequestTransactionStates": [],
"identityStatus": []
}
2. (Optional) Create Webhook Secret
Create a webhook secret that enables you to verify webhook notifications. BitGo uses this secret to generate HMAC-SHA256 signatures for all webhook payloads, enabling you to verify that notifications genuinely originate from BitGo and that no one has tampered with them.
Important
Store your webhook secret securely. If compromised, rotate it immediately by calling this endpoint again. You can rotate secrets once every 5 minutes.
Note
You must create your webhook secret before performing the action that triggers the notification. Otherwise, the notification won't include a signature and you won't be able to verify it.
Endpoint: Create Webhook Secret
export ENTERPRISE_ID="<YOUR_ENTERPRISE_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
curl -X POST \
https://app.bitgo-test.com/api/v2/webhook/secret \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"enterpriseId": "'"$ENTERPRISE_ID"'"
}'
Step Result
{
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"
}
3. (Optional) Verify Webhook Notification
When BitGo sends a webhook notification, it includes an x-signature-sha256 header containing an HMAC-SHA256 signature. You can verify the notification is legitimate by passing the signature from this header along with the payload you received.
Endpoint: Verify Webhook Notification
export WEBHOOK_ID="<YOUR_WEBHOOK_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export SIGNATURE="<X_SIGNATURE_SHA256_HEADER_VALUE>" # Value from the x-signature-sha256 header in the webhook notification
export PAYLOAD="<YOUR_PAYLOAD>" # JSON payload as a string from the webhook notification body
curl -X POST "https://app.bitgo-test.com/api/v2/webhook/$WEBHOOK_ID/verify" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"signature": "'"$SIGNATURE"'",
"notificationPayload": "'"$PAYLOAD"'"
}'
Step Result
{
"webhookId": "wh119ecd15a4adf811f8f552fde21b9d819b4dc9a7f04c51513395816703c73511",
"isValid": true
}
4. (Optional) Simulate Enterprise Webhook
You can simulate your webhook with real data or with placeholder data (also known as dummy data).
Endpoint: Simulate Enterprise Webhook
export ENTERPRISE_ID="<YOUR_ENTERPRISE_ID>"
export WEBHOOK_ID="<YOUR_WEBHOOK_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export ACCESS_TOKEN_ID="<YOUR_ACCESS_TOKEN_ID>"
curl -X GET "https://app.bitgo-test.com/api/v2/enterprise/$ENTERPRISE_ID/webhook/$WEBHOOK_ID/simulate" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"accessTokenId": "'"$ACCESS_TOKEN_ID"'"
}'
Step Result
{
"webhookNotifications": [
{
"id": "59cd72485007a239fb00282ed480da1f",
"type": "accessToken",
"enterprise": "62c5ae8174ac860007aff138a2d74df7",
"url": "https://webhook.site/f74addc1-c40a-4fce-879a-2d92b8d491c5",
"state": "sent_via_ncc",
"simulation": true,
"retries": 0,
"webhook": "68531e154d28af627819bd3e183a930e",
"updatedAt": "2025-06-19T20:48:43.525Z",
"idempotencyKey": "a1b2c3d4e5f6g7h8",
"payload": "string",
"response": {}
}
]
}