> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blnkfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling Notifications

> Configure real-time alerts for transaction events and handle error notifications through webhooks. Stay updated with critical information.

## Overview

Blnk uses webhooks to send you real-time notifications about crucial transaction events and system errors happening in your ledger. Our webhook follows this structure:

```json theme={"system"}
{
  "event": "name_of_event",
  "data": {
    "field": "value"
  }
}
```

| Field   | Description                                                       | Type   |
| :------ | :---------------------------------------------------------------- | :----- |
| `event` | Indicates the type of event, e.g., `transaction.applied`.         | String |
| `data`  | Contains the request payload, detailing the relevant information. | Object |

***

## Supported events

Here are a list of supported events:

### Ledgers

| **Event name**   | **Description**           |
| :--------------- | :------------------------ |
| `ledger.created` | When a ledger is created. |

### Balances

| **Event name**    | **Description**            |
| :---------------- | :------------------------- |
| `balance.created` | When a balance is created. |

### Balance monitors

| **Event name**    | **Description**                                                   |
| :---------------- | :---------------------------------------------------------------- |
| `balance.monitor` | When a balance monitor is triggered due its conditions being met. |

### Transactions

| **Event name**          | **Description**                               |
| :---------------------- | :-------------------------------------------- |
| `transaction.applied`   | When a transaction is applied or committed.   |
| `transaction.inflight`  | When a transaction is inflight                |
| `transaction.void`      | When an inflight transaction is voided.       |
| `transaction.scheduled` | When a transaction is successfully scheduled. |
| `transaction.rejected`  | When a transaction is rejected                |

### Bulk transactions

| **Event name**              | **Description**                                                  |
| :-------------------------- | :--------------------------------------------------------------- |
| `bulk_transaction.applied`  | When all items in a bulk transactions have been applied.         |
| `bulk_transaction.inflight` | When all items in a bulk transactions are successfully inflight. |
| `bulk_transaction.failed`   | When a bulk transaction fails.                                   |

### Identities

| **Event name**     | **Description**              |
| :----------------- | :--------------------------- |
| `identity.created` | When an identity is created. |

### System errors

<Info>
  Available in version 0.12.0 and later.
</Info>

| **Event name** | **Description**                                        |
| :------------- | :----------------------------------------------------- |
| `system.error` | When a system error occurs (e.g. duplicate reference). |

```json Example payload theme={"system"}
{
  "event": "system.error",
  "data": {
    "error": "reference 0x8fa3c1a2b7d9_q has already been used",
    "time": "2025-12-08T10:30:45Z"
  }
}
```

| **Field** | **Type** | **Description**                                       |
| :-------- | :------- | :---------------------------------------------------- |
| `error`   | String   | The error message describing what went wrong.         |
| `time`    | String   | ISO 8601 formatted timestamp when the error occurred. |

***

## Handling error notifications

Blnk provides multiple ways to handle error notifications:

1. **System error webhooks**: System errors (e.g., duplicate reference, queue failures, etc.) are automatically sent as webhooks to your configured webhook URL. These help you monitor and troubleshoot system issues in real-time.

2. **Slack notifications**: You can also send error notifications to Slack via a specified webhook provided by your Slack workspace. This is useful for team alerts and monitoring.

To learn how to get your Slack webhook URL, go to: [Slack API: Sending messages using incoming webhooks](https://api.slack.com/messaging/webhooks).

***

## Configuring notifications in Blnk

To set up how you receive notifications from Blnk, you need to update your `blnk.json` configuration file. This file allows you to specify the webhook URLs that Blnk sends notifications to — both for your application and Slack.

<Warning>
  If you do not have a `blnk.json` file, please create a new `json` file — it contains essential settings for running your Blnk server.

  Next, copy and paste the [notification configuration example](/advanced/configuration/notifications) into it.
</Warning>

Update the `notification` object as follows:

* `slack`:
  * `webhook_url`: The webhook URL provided by your Slack workspace.
* `webhook`:
  * `url`: Your application's webhook URL where Blnk sends transaction event notifications to.
  * `headers`: Optional headers that you may need to include in the notification request to authenticate the message. This can include authentication tokens or content type specifications.

Below is an example of a notifications configuration:

```json blnk.json theme={"system"}
{
  "notification": {
    "slack": {
      "webhook_url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
    },
    "webhook": {
      "url": "http://server:5001/webhooks",
      "headers": {
        "Content-Type": "application/json",
        "Authorization": "Bearer <your_auth_token>"
      }
    }
  }
}
```

***

## Webhook security (signature verification)

<Info>
  Available in version 0.13.0 and later.
</Info>

Blnk signs outbound webhook requests so you can verify they came from your Blnk Core and were not tampered with. Both **notification webhooks** and [transaction hooks](/hooks/overview) (PRE/POST) use the same signing scheme.

### Headers

| Header             | Description                                                                               |
| :----------------- | :---------------------------------------------------------------------------------------- |
| `X-Blnk-Signature` | Hex-encoded HMAC-SHA256 of the signed payload.                                            |
| `X-Blnk-Timestamp` | Unix timestamp in seconds (string), used in the signed payload and for replay protection. |
| `X-Hook-ID`        | The hook identifier (hooks only).                                                         |
| `X-Hook-Type`      | `PRE_TRANSACTION` or `POST_TRANSACTION` (hooks only).                                     |

### Verifying signatures

<Steps titleSize="h4">
  <Step title="Extract headers and raw body">
    Read `X-Blnk-Signature` and `X-Blnk-Timestamp` from the request. Reject requests missing either header.

    <Warning>
      Preserve the **exact raw bytes** of the request body before JSON parsing. Do not use a parsed or re-serialized body—any whitespace or encoding changes will cause verification to fail.
    </Warning>
  </Step>

  <Step title="Build signed payload">
    Concatenate the timestamp and raw body:

    ```
    signed = timestamp + "." + rawRequestBody
    ```
  </Step>

  <Step title="Compute expected signature">
    Compute `HMAC-SHA256` using `server.secret_key` from your [Blnk configuration](/advanced/secure-blnk), then hex-encode:

    ```
    expected = hex( HMAC-SHA256(secret_key, signed) )
    ```
  </Step>

  <Step title="Compare signatures">
    Compare `expected` to `X-Blnk-Signature` using a **constant-time comparison** (e.g. `crypto.timingSafeEqual` in Node.js). If they match, the webhook is authentic.

    <Tip>
      For replay protection, also reject timestamps outside a small window (e.g. ±5 minutes).
    </Tip>

    <CodeGroup>
      ```javascript Node.js theme={"system"}
      import express from "express";
      import crypto from "crypto";

      const app = express();
      const SECRET = process.env.BLNK_SECRET; // must match server.secret_key

      app.use(express.json({ verify: (req, _, buf) => { req.rawBody = buf; } }));

      app.post("/webhook", (req, res) => {
        const sig = req.header("x-blnk-signature");
        const ts = req.header("x-blnk-timestamp");
        if (!sig || !ts) return res.sendStatus(400);

        const expected = crypto.createHmac("sha256", SECRET)
          .update(`${ts}.${req.rawBody}`)
          .digest("hex");

        if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
          return res.sendStatus(401);

        res.sendStatus(200);
      });

      app.listen(3000);
      ```

      ```go Go theme={"system"}
      package main

      import (
        "crypto/hmac"
        "crypto/sha256"
        "encoding/hex"
        "io"
        "net/http"
        "os"
      )

      var secret = []byte(os.Getenv("BLNK_SECRET")) // must match server.secret_key

      func webhook(w http.ResponseWriter, r *http.Request) {
        sig := r.Header.Get("X-Blnk-Signature")
        ts := r.Header.Get("X-Blnk-Timestamp")
        if sig == "" || ts == "" {
          http.Error(w, "missing headers", http.StatusBadRequest)
          return
        }

        body, _ := io.ReadAll(r.Body)
        mac := hmac.New(sha256.New, secret)
        mac.Write([]byte(ts + "." + string(body)))
        expected := hex.EncodeToString(mac.Sum(nil))

        if !hmac.Equal([]byte(sig), []byte(expected)) {
          http.Error(w, "invalid signature", http.StatusUnauthorized)
          return
        }
        w.WriteHeader(http.StatusOK)
      }

      func main() {
        http.HandleFunc("/webhook", webhook)
        http.ListenAndServe(":3000", nil)
      }
      ```

      ```python Python theme={"system"}
      import hmac, hashlib, os
      from flask import Flask, request, abort

      app = Flask(__name__)
      SECRET = os.environ.get("BLNK_SECRET").encode()  # must match server.secret_key

      @app.route("/webhook", methods=["POST"])
      def webhook():
          sig = request.headers.get("X-Blnk-Signature")
          ts = request.headers.get("X-Blnk-Timestamp")
          if not sig or not ts:
              abort(400)

          signed = f"{ts}.".encode() + request.get_data()
          expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()

          if not hmac.compare_digest(sig, expected):
              abort(401)
          return "", 200

      if __name__ == "__main__":
          app.run(port=3000)
      ```
    </CodeGroup>
  </Step>
</Steps>

***

## Need help?

We are very happy to help you make the most of Blnk, regardless of whether it is your first time or you are switching from another tool.

To ask questions or discuss issues, please [contact us](mailto:support@blnkfinance.com) or [join our Discord community](https://discord.gg/7WNv94zPpx).

***

<Tip>
  **Tip:** Connect to Blnk Cloud to see your Core data.

  You can view your transactions, manage identities, create custom reports, invite other team members to collaborate, and perform operations on your Core — all in one dashboard.

  [Check out Blnk Cloud →](https://www.blnkfinance.com/products/cloud)
</Tip>
