Multi-party computation (MPC) operations, such as generating keys and authorizing transactions, 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 a REST API endpoint to receive webhook notifications generated by Fireblocks. Each of these notifications carries essential data that should be processed by the Software Development Kit (SDK) to handle the appropriate step in the asynchronous process.

πŸ“˜

Please make sure to go over the 'Development' -> 'Webhook Configuration' section first

POST /api/webhook - is exposed for incoming webhook notification send by Fireblocks. This endpoint is controlled by the following controller:

/src/controllers/webhook.controller.ts/WebhookController

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);
    }
  }

In addition, to validation of the webhook signature is done with the following middleware:

ncw-backend-demo/src/middleware/webhook.ts

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`));
    }
  };

While the route is defined in:

/src/routes/webhook.route.ts

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;
}