Overview
Webhooks play a vital role in enabling event-driven architectures within Fireblocks. They are particularly valuable for automating and streamlining processes such as deposit handling, withdrawal workflows, and other internal operations. By using webhooks, you can build responsive systems that react to various activities in your workspace, reducing manual intervention and improving overall efficiency. With webhooks, your application can stay synchronized with your Fireblocks environment, ensuring seamless integration and operational agility.
Note:
If you are using webhooks v1 and want to migrate to webhooks v2, please refer to this migration doc.
Getting Started with Webhooks
To receive real-time updates via webhooks, follow these steps to configure and test the Webhooks v2 service.
Step 1: Create a Webhook
Depending on your preference and setup, you can create webhooks using either the Fireblocks Console or the Fireblocks SDK.
Create via the Fireblocks Console
To create, follow these steps in the Fireblocks Console:
- Log in to your Fireblocks Console and navigate to the Developer Center.

- Click the Webhooks tab.

- Click the Create Webhook to create a webhook.

- In the Create Webhook modal:
- Enter the HTTPS endpoint URL where your application will receive the webhook notifications. (Required)
- Fill in the description. (Optional)
- Select the webhook status. (Required. By default, the status is Active)
- Select the events you wish to subscribe to. You can subscribe to a specific event or a category. Subscribing to a category will not automatically include future events under that category.
- Click Create Webhook to finalize your configuration.

Once your Webhook is connected to your Fireblocks workspace, you will start receiving notifications on events in that workspace.
Create via the Fireblocks SDK
You can also configure webhooks programmatically by making an API request. Here's how:
- Obtain your Fireblocks API key with the required permissions to manage webhooks.
- Use the POST /webhooks endpoint to set up your webhook URL.
Example of configuring new URL via the Fireblocks SDK:
import { readFileSync } from 'fs';
import { Fireblocks, BasePath } from '@fireblocks/ts-sdk';
import type { FireblocksResponse, WebhooksV2BetaApiCreateWebhookRequest, Webhook } from '@fireblocks/ts-sdk';
// Set the environment variables for authentication
process.env.FIREBLOCKS_BASE_PATH = BasePath.Sandbox; // or assign directly to "https://sandbox-api.fireblocks.io/v1"
process.env.FIREBLOCKS_API_KEY = "my-api-key";
process.env.FIREBLOCKS_SECRET_KEY = readFileSync("./fireblocks_secret.key", "utf8");
const fireblocks = new Fireblocks();
let body: WebhooksV2BetaApiCreateWebhookRequest = {
// CreateWebhookRequest
createWebhookRequest: param_value,
// string | A unique identifier for the request. If the request is sent multiple times with the same idempotency key, the server will return the same response as the first request. The idempotency key is valid for 24 hours. (optional)
idempotencyKey: idempotencyKey_example,
};
fireblocks.webhooksV2Beta.createWebhook(body).then((res: FireblocksResponse<Webhook>) => {
console.log('API called successfully. Returned data: ' + JSON.stringify(res, null, 2));
}).catch((error:any) => console.error(error));
Webhook URL requirement
Each webhook URL must be a complete, globally available HTTPS address (e.g.,
https://example.com/webhook
).
List of Event Types and Categories
Category | Event Type | Description |
---|---|---|
Transactions |
transaction.created
|
Triggered when any new transaction is identified in the workspace. |
Transactions |
transaction.status.updated
|
Triggered when there is any change in a transaction's status or when Fireblocks detects an update to the number of confirmations. |
Transactions |
transaction.approval_status.updated
|
Triggered on every approval based on the transaction authorization policy. |
Whitelist |
external_wallet.asset.added
|
Triggered when any asset is added to an external wallet. |
Whitelist |
external_wallet.asset.removed
|
Triggered when any asset is removed from an external wallet. |
Whitelist |
contract_wallet.asset.added
|
Triggered when any asset is added to a contract wallet. |
Whitelist |
contract_wallet.asset.removed
|
Triggered when any asset is removed from a contract wallet. |
Wallet |
vault_account.created
|
Triggered when a vault account is created. |
Wallet |
vault_account.asset.added
|
Triggered when any asset is added to a vault account. |
Wallet |
vault_account.asset.balance_updated
|
Triggered when a vault account's asset balance is updated. |
CeFi |
exchange_account.added
|
Triggered when any exchange account is added. |
CeFi |
fiat_account.added
|
Triggered when any fiat account is added. |
Network Connection |
network_connection.added
|
Triggered when any Fireblocks network connection is added. |
Smart Transfer |
ticket.created
|
Triggered when a smart transfer ticket is created. |
Smart Transfer |
ticket.submitted
|
Triggered when a smart transfer ticket is submitted. |
Smart Transfer |
ticket.expired
|
Triggered when a smart transfer ticket expires. |
Smart Transfer |
ticket.canceled
|
Triggered when a smart transfer ticket is canceled. |
Smart Transfer |
ticket.fulfilled
|
Triggered when a smart transfer ticket is funded. |
Smart Transfer |
ticket.counterparty.added
|
Triggered when a counterparty is added to a smart transfer ticket. |
Off Exchange |
settlement.created
|
A notification is sent when a new settlement is initiated in the workspace. |
Step 2: Test the Webhook configuration
Testing via Dashboard
After configuring the webhook, it’s important to verify that your application is ready to process incoming requests:
- Fire a test event from the Fireblocks Console to ensure your endpoint is reachable and correctly handles the event payload.
- Review the logs in your application to confirm that events are being processed as expected.
Testing via SDK
You can test the Fireblocks webhook events via SDK by following the next steps:
- Fire a test event via the Fireblocks SDK:
import { readFileSync } from 'fs';
import { Fireblocks, BasePath } from '@fireblocks/ts-sdk';
import type { FireblocksResponse, WebhooksV2BetaApiGetNotificationsRequest, NotificationPaginatedResponse } from '@fireblocks/ts-sdk';
// Set the environment variables for authentication
process.env.FIREBLOCKS_BASE_PATH = BasePath.Sandbox; // or assign directly to "https://sandbox-api.fireblocks.io/v1"
process.env.FIREBLOCKS_API_KEY = "my-api-key";
process.env.FIREBLOCKS_SECRET_KEY = readFileSync("./fireblocks_secret.key", "utf8");
const fireblocks = new Fireblocks();
let body: WebhooksV2BetaApiGetNotificationsRequest = {
// string
webhookId: 44fcead0-7053-4831-a53a-df7fb90d440f,
// 'ASC' | 'DESC' | ASC / DESC ordering (default DESC) (optional)
order: 'ASC',
// string | Cursor of the required page (optional)
pageCursor: "pageCursor_example",
// number | Maximum number of items in the page (optional)
pageSize: 10,
// string | sort by start date (optional)
createdStartDate: 2024-09-24T09:14:38.356Z,
// string | sort by end date (optional)
createdEndDate: 2024-09-24T09:14:38.356Z,
// Array<NotificationStatus> | Filter by Notification statues (optional)
statuses: COMPLETED,
// Array<WebhookEvent> | Filter by Notification eventTypes (optional)
eventTypes: ["transaction.created","transaction.status.updated"],
// string | Filter by resourceId (optional)
resourceId: resourceId_example,
};
fireblocks.webhooksV2Beta.getNotifications(body).then((res: FireblocksResponse<NotificationPaginatedResponse>) => {
console.log('API called successfully. Returned data: ' + JSON.stringify(res, null, 2));
}).catch((error:any) => console.error(error));
- Run a webhook listener on your end:
const crypto = require("crypto");
const express = require("express");
const bodyParser = require('body-parser')
const port = 3000;
const publicKey = `-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG
jjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5
YlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v
4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH
clM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd
CGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ
dE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd
HgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP
SwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1
4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc
IMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk
tSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==
-----END PUBLIC KEY-----`.replace(/\\n/g, "\n");
const app = express();
app.use(bodyParser.json());
app.post("/webhook", (req, res) => {
const message = JSON.stringify(req.body);
const signature = req.headers["fireblocks-signature"];
const verifier = crypto.createVerify('RSA-SHA512');
verifier.write(message);
verifier.end();
const isVerified = verifier.verify(publicKey, signature, "base64");
console.log("Verified:", isVerified);
res.send("ok");
});
app.listen(port, () => {
console.log(`Webhook running at http://localhost:${port}`);
});
import falcon
import json
import rsa
import base64
FIREBLOCKS_PUBLIC_KEY = """
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG
jjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5
YlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v
4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH
clM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd
CGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ
dE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd
HgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP
SwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1
4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc
IMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk
tSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==
-----END PUBLIC KEY-----
"""
signature_pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(FIREBLOCKS_PUBLIC_KEY)
class RequestBodyMiddleware(object):
def process_request(self, req, resp):
req.body = req.bounded_stream.read()
class AuthMiddleware(object):
def process_request(self, req, resp):
signature = req.get_header('Fireblocks-Signature')
if signature is None:
raise falcon.HTTPUnauthorized('Signature required')
if not self._signature_is_valid(req.body, signature):
raise falcon.HTTPUnauthorized('Invalid signature')
def _signature_is_valid(self, body, signature):
try:
hashing_alg = rsa.verify(body, base64.b64decode(signature), signature_pub_key)
return hashing_alg == "SHA-512"
except rsa.pkcs1.VerificationError:
return False
class DummyRequest(object):
def on_post(self, req, resp):
obj = json.loads(req.body.decode("utf-8"))
print(obj)
resp.status = falcon.HTTP_201
# Create falcon app
app = falcon.API(
middleware=[
RequestBodyMiddleware(),
AuthMiddleware()
]
)
app.add_route('/webhook', DummyRequest())
if __name__ == '__main__':
from wsgiref import simple_server # NOQA
httpd = simple_server.make_server('127.0.0.1', 8000, app)
httpd.serve_forever()
You can validate Fireblocks webhook events by validating the signature attached in the request header:
Fireblocks-Signature: Base64(RSA512(_WEBHOOK_PRIVATE_KEY_, SHA512(eventBody)))
To validate the signature in Production (Mainnet and Testnet) workspaces, please use the public key below:
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG
jjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5
YlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v
4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH
clM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd
CGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ
dE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd
HgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP
SwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1
4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc
IMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk
tSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==
-----END PUBLIC KEY-----
To validate the signature in Sandbox workspaces, please use the public key below:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw+fZuC+0vDYTf8fYnCN6
71iHg98lPHBmafmqZqb+TUexn9sH6qNIBZ5SgYFxFK6dYXIuJ5uoORzihREvZVZP
8DphdeKOMUrMr6b+Cchb2qS8qz8WS7xtyLU9GnBn6M5mWfjkjQr1jbilH15Zvcpz
ECC8aPUAy2EbHpnr10if2IHkIAWLYD+0khpCjpWtsfuX+LxqzlqQVW9xc6z7tshK
eCSEa6Oh8+ia7Zlu0b+2xmy2Arb6xGl+s+Rnof4lsq9tZS6f03huc+XVTmd6H2We
WxFMfGyDCX2akEg2aAvx7231/6S0vBFGiX0C+3GbXlieHDplLGoODHUt5hxbPJnK
IwIDAQAB
-----END PUBLIC KEY-----
Payload Structure
Fireblocks webhook notifications are sent with the following structure:
Field | Type | Description |
---|---|---|
id | string (UUID) | Unique identifier for the webhook notification object. |
eventType | enum | Description of the event (e.g., transaction.created ). |
resourceId | string (UUID) | (Optional) ID of the entity that triggered the event (e.g., txid ). |
data | object | Contains the API resource relevant to the event (e.g., the full transaction object for a transaction.created event). |
createdAt | number | Webhook timestamp. |
workspaceId | string (UUID) | The tenant or workspace identifier associated with the event. |
The data
object is different per each event type. Below is the example of the payload.
{
"id": "........-....-....-....-............",
"url”: "webhooksURL",
"description”: "A description of the webhook",
"events”: {
transaction.created*,
transaction.updated
},
"status": enabled,
"createdAt": 1679651104380,
"lastUpdated": 1679651104380
}
Best Practices for Using Webhooks
- Handle events asynchronously
Configure your handler to process incoming events with an asynchronous queue. Processing transactions synchronously can lead to long response times and potential timeouts, especially during periods of high market volatility when transaction counts spike. If all events are handled synchronously, sudden surges in activity could overwhelm your system and degrade performance.Asynchronous queues allow you to manage these transaction bursts efficiently, ensuring your system processes events at a sustainable rate without bottlenecks or failures."
- Respond with a 2xx Status Promptly
Your endpoint should return a successful 2xx status code as soon as possible, before executing any time-consuming logic that might lead to a timeout. For instance, send a 200 response before processing tasks like marking a customer’s invoice as paid in your accounting system.
- Managing Duplicate Events
Your webhook endpoint may sometimes receive the same event multiple times. To prevent duplicate processing, track event IDs and ignore any that have already been handled. Additionally, ensure you're processing the most recent event by comparing timestamps in the createdAt
field, rather than relying solely on the event ID.
Event Delivery Behavior
Retry Behavior
The Fireblocks server expects an HTTP-200 (OK) response to confirm that a webhook notification has been successfully received. All webhook events must respond with this status to indicate successful processing.
If no response is received, Fireblocks will automatically retry the request several times based on the following schedule (in seconds): 0, 30, 120, 300, 900, 1800, 3600, 7200, 14400.
Retries will not be attempted for client-side errors (HTTP 4xx) except for the following cases:
- 429 - Too Many Requests: Indicates rate limits have been exceeded.
- 408 - Request Timeout: Indicates the server took too long to respond.
After a total of 10 failed attempts, the notification will be marked as failed and no further retries will be made.
Event Ordering
Although Fireblocks strives to send notifications in order (per resource), we cannot guarantee that events will always be delivered in the sequence they are generated. We recommend designing your endpoint to process events independently, without relying on their delivery order.
Circuit Breaker
Fireblocks introduces a circuit breaker mechanism for managing faulty webhook endpoints to ensure fairness and maintain high throughput across all users. This feature helps identify and suspend endpoints that consistently fail, ensuring that system resources are optimized and reliable for everyone.
The circuit breaker monitors error rates for each webhook endpoint and applies suspension policies based on predefined thresholds. These policies vary depending on the type and severity of the errors:
Webhooks with an error rate exceeding a specific threshold will be automatically disabled to protect system integrity. To prevent performance issues in your application, handle events asynchronously with queues and quickly return a 2xx status code.
Event Filtering
You can filter events by their eventType, reducing the need to process unwanted events.
Debugging
Missed webhook notifications can occur due to various issues such as temporary downtime, connectivity problems, or unexpected errors in your webhook handler. These issues can prevent your server from acknowledging or processing events sent by Fireblocks. To ensure no critical events are lost, Fireblocks provides API endpoints to resend webhook notifications as needed.
Here are the available endpoints and when you might use them:
Note:
The endpoint number 2 and 3 are currently under development.
1. Resend a Single Notification
Endpoint: `POST /webhooks/{webhookId}/notifications/{notificationId}/resend`
This endpoint lets you resend a single notification by its unique identifier (notificationId
). It is ideal when you want to troubleshoot or ensure the delivery of a specific event, such as a transaction update or status change.
Example Use Case:
If your server fails to process a specific transaction notification, you can use this endpoint to retry just that notification, minimizing unnecessary retries.
2. Resend Multiple Notifications by Resource
Endpoint: POST /webhooks/{webhookId}/notifications/resend_by_resource
This endpoint allows you to resend multiple notifications for all failed webhooks associated with a specific resourceId
. This is particularly useful in scenarios where you need to retry notifications for a specific resource, such as a vault account or transaction, after resolving server-side issues.
Example Use Case:
If your webhook server missed updates for a specific transaction due to an outage, you can use this endpoint to retrieve all missed notifications related to that transaction.
3. Recover from downtime
Endpoint: `POST /webhooks/{webhookId}/notifications/resend_failed`
This endpoint resends all failed webhook notifications for a given webhook. It is a comprehensive solution for situations where your server experienced extended downtime or a widespread failure. You can set the resend duration for up to 24 hours.
Example Use Case:
If your webhook server was offline for an hour due to a deployment or network issue, you can use this endpoint to ensure all missed events during that time are resent.