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's KYC status change or a new bank account added to an end-user's 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 - The signature is sent 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 | An access token is made. |
bankAccount | A bank account is added. |
reversal | A fiat transaction is reversed. |
Organization Webhook Types
| Type | Triggers When |
|---|---|
accessToken | An access token is made. |
bankAccount | A bank account is added. |
enterpriseKycState | Enterprise KYC status changes. |
identityStatus | A user's identity status changes. |
idvStatus | A user's IDV status changes. |
pendingapproval | A transaction request is pending approval. |
reversal | A fiat transaction is reversed. |
transfer | A transfer succeeds or fails. |
txRequest | A transaction request changes status. |
txRequestTransaction | A transaction request transaction state changes. |
userKycState | A user's KYC verification status changes. |
tradeOrder | A trade order's status changes. |
Webhook Payload Schemas
Each webhook type returns a different JSON payload. Use these example schemas to parse and handle incoming webhook notifications.
Recipes
Need just the steps? Expand a recipe below to get started:
Triggered when an access token is created.
{
"userId": "59cd72485007a239fb00282a",
"username": "[email protected]",
"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 bank account is added 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's KYC verification status changes.
Note: The
kycStatefield can beapproved,rejected, orpending.
{
"organizationId": "695d68b362ce13d38fa6a4fe2c7a6a82",
"kycState": "approved",
"type": "enterpriseKycState",
"id": "cmlqyh7x4001m0dix1rypheuq",
"enterpriseId": "6994b772b8f7aead2dd0e4c42d1181fe"
}Triggered when a user's 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's IDV (Identity Verification) status changes.
{
"type": "idvStatus",
"status": "approved",
"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 a fiat transaction is reversed.
{
"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."
}
}Triggered when an on-chain transaction request changes status.
Note: Go Account to Go Account transfers are off-chain (BitGo's 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's 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's KYC verification status changes.
Note: The
kycStatefield can beapproved,rejected, orpending.
{
"organizationId": "695d68b362ce13d38fa6a4fe2c7a6a82",
"kycState": "approved",
"type": "userKycState",
"id": "cmlqz57ct001j0esb6vde1qgp",
"userId": "6994bbd53763ba8c4e74ff38766c0ba8"
}Triggered when a trade order's status changes.
Note: The
statusfield can bepending_open,open,completedwith nullsettleDate, orcompletedwith populatedsettleDate.
{
"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 bank account is added.
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
{
"success": "true",
"data": {
"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": []
}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 haven't been tampered with.
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 an access token is created.
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": []
}
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 haven't been tampered with.
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 POST "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",
"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": 2,
"allowBlockedHosts": true,
"payload": "string",
"response": {
"code": 0,
"type": "string",
"body": "string",
"error": "string"
}
}
]
}See Also
Updated 2 months ago