🚧

Before you begin

We recommend reading the Webhook Configuration page first.

Overview

Multi-party computation (MPC) operations involve communication between several participants. This collaboration occurs over multiple rounds of communication, creating an asynchronous process.

To facilitate this communication, your system needs to expose the POST /api/webhook REST API endpoint to receive webhook notifications generated by Fireblocks. Each notification carries essential data to the Software Development Kit (SDK), which handles the asynchronous process.

Webhook Controller

The POST /api/webhook endpoint is controlled by the /src/controllers/webhook.controller.ts/WebhookController controller:

async handle(req: Request, res: Response, next: NextFunction) {
    try {
      const { type, timestamp } = req.body;
      console.log(
        `received webhook, type: ${type} timestamp: ${timestamp} body: ${JSON.stringify(
          req.body,
        )} `,
      );

      switch (type) {
        case "TRANSACTION_CREATED": {
          const { data } = req.body as ITransactionCreatedMessagePayload;
          const { id, status } = data;
          await patchTransactionAmountUsd(data, this.clients.cmc);
          await handleTransactionCreated(id, status, data);
          return res.status(200).send("ok");
        }

        case "TRANSACTION_STATUS_UPDATED": {
          const { data } = req.body as ITransactionCreatedMessagePayload;
          const { id, status } = data;
          await patchTransactionAmountUsd(data, this.clients.cmc);
          await handleTransactionStatusUpdated(id, status, data);
          return res.status(200).send("ok");
        }

        case "ON_NEW_EXTERNAL_TRANSACTION":
        case "VAULT_ACCOUNT_ADDED":
        case "VAULT_WALLET_READY":
        case "UNMANAGED_WALLET_ADDED":
        case "UNMANAGED_WALLET_REMOVED":
        case "THIRD_PARTY_ACCOUNT_ADDED":
        case "NETWORK_CONNECTION_ADDED":
        case "NETWORK_CONNECTION_REMOVED":
        case "CONFIG_CHANGE_REQUEST_STATUS":
        case "TRANSACTION_APPROVAL_STATUS_UPDATED":
        case "VAULT_ACCOUNT_ASSET_ADDED":
        case "EXTERNAL_WALLET_ASSET_ADDED":
        case "INTERNAL_WALLET_ASSET_ADDED":
          return res.status(200).send("ok");

        default:
          console.error(`unknown webhook type ${type}`);
          return res.status(400).send("error");
      }
    } catch (err) {
      return next(err);
    }
  }

Webhook Signature Validation

The ncw-backend-demo/src/middleware/webhook.ts middleware validates the webhook signature:

export const validateWebhook =
  (publicKey: string) => (req: Request, res: Response, next: NextFunction) => {
    const message = JSON.stringify(req.body);
    const signature = req.headers["fireblocks-signature"];

    if (typeof signature !== "string") {
      next(new Error(`Invalid signature`));
      return;
    }

    const verifier = crypto.createVerify("RSA-SHA512");
    verifier.write(message);
    verifier.end();

    const isVerified = verifier.verify(publicKey, signature, "base64");
    if (isVerified) {
      next();
    } else {
      next(new Error(`Invalid signature`));
    }
  };

Webhook Route

The /src/routes/webhook.route.ts route defines the webhook route:

export function createWebhook(clients: Clients, publicKey: string) {
  const contoller = new WebhookController(clients);
  const route = Router({ mergeParams: true });
  route.post("/", validateWebhook(publicKey), contoller.handle.bind(contoller));
  return route;
}