# Webhooks Setup

Webhooks enable real-time communication by notifying your server when specific events occur in the Nabla Core API. This guide will help you set up a secure webhook to receive these event notifications.

## Expose an endpoint[​](#expose-an-endpoint "Direct link to Expose an endpoint")

To integrate webhooks with the Nabla Core API, you must provide a public `HTTP` endpoint that accepts `POST` requests. For security reasons, the endpoint must support TLS (HTTPS).

When your endpoint successfully processes an event, it should return a `200 OK` response. If not, the Nabla Core API will continue retrying the delivery until successful or after multiple failed attempts.

For testing, you can set up a mock server using tools like Ngrok or Postman to simulate the endpoint.

## Add its URL in the API Admin Console[​](#add-its-url-in-the-api-admin-console "Direct link to Add its URL in the API Admin Console")

Create a new webhook in *[Webhooks](http://pro.nabla.com/developers/webhoooks)* from the API Admin Console

![Example webhook created on the API Admin Console](/img/api-admin-console-setup-webhooks.png)

## Authentication[​](#authentication "Direct link to Authentication")

Because your endpoint is publicly accessible, it is crucial to only accept requests that include a valid signature from Nabla. Neglecting to verify the timestamp or signature could leave your system vulnerable to replay attacks or unauthorized webhook requests.

### Verify signature[​](#verify-signature "Direct link to Verify signature")

All webhook requests to your provided URL will include the following headers:

* `"x-nabla-webhook-timestamp"`: the timestamp indicating when the webhook request was made.
* `"x-nabla-webhook-signature"`: a signature (or multiple signatures) used to verify the authenticity and integrity of the request. Each signature is generated using a unique *signature secret key*.

We sign each webhook call using the HMAC (Hash-based Message Authentication Code) algorithm with SHA-256. The signature is generated based on the following:

* The shared *signature secret key* (available in the admin console, see screenshot)
* The timestamp from the `"x-nabla-webhook-timestamp"` header
* The entire body of the webhook request

These signatures are included in the `"x-nabla-webhook-signature"` header.

To securely integrate with Nabla Core API webhooks, follow these verification steps upon receiving `POST` requests:

* Extract the timestamp and signature headers.
* **Reject** the request if the timestamp is too old (allow a maximum of 60 seconds).
* Concatenate the timestamp with the request body.
* Compute the HMAC using the shared signature secret key and the SHA-256 algorithm.
* Compare the computed HMAC with the received signatures.
* **Reject** the request (respond with an HTTP 401) if none of the received signatures match the computed signature.
* **Reject** the request if the webhook event ID in the payload has already been processed.

Here’s a JavaScript sample code for the signature verification described above:

```
const bodyParser = require("body-parser");

const crypto = require("crypto");

const express = require("express");



// New app using express module

const app = express();



app.use(

  bodyParser.json({

    type: "application/json",

    verify: function (req, res, buf, encoding) {

      // Replace with your own Webhook Signature Secret Key

      const webhookSecretKey = "<WEBHOOK_SECRET_KEY>";



      const timestamp = req.headers["X-Nabla-Webhook-Timestamp"];

      const receivedSignatures = req.headers["X-Nabla-Webhook-Signature"];



      const computedSignature = crypto

        .createHmac("sha256", webhookSecretKey)

        .update(timestamp + buf)

        .digest("hex");



      const signatureMatch = receivedSignatures

        .split(",")

        .some(

          (receivedSignature) => receivedSignature.trim() === computedSignature

        );



      console.log("----- RECEIVED & COMPUTED SIGNATURES -----");

      console.log(`Received signature: ${receivedSignatures}`);

      console.log(`Computed signature: ${computedSignature}`);

      console.log(`Do any of the signatures match? ${signatureMatch}`);



      if (!signatureMatch) {

        return res.status(401).send("Signature invalid");

      }

    },

  })

);
```

For more details on HMAC-SHA256 please refer to [RFC-2104](https://datatracker.ietf.org/doc/html/rfc2104) and [RFC-4231](https://datatracker.ietf.org/doc/html/rfc4231).

### Signature secret key rotation[​](#signature-secret-key-rotation "Direct link to Signature secret key rotation")

Regularly rotate the shared *signature secret key* in the admin console.

After pressing the “rotate” button, a new secret will be generated and displayed. Upon initiating a rotation, promptly release an update with the new key. Nabla servers will continue to support the previous keys for a short period to ensure backward compatibility during the update process.

**Make sure your servers support verifying multiple signatures** to facilitate this transition smoothly. You also have the option to immediately erase old keys, for instance, if you suspect a key has been compromised.

## Webhook events[​](#webhook-events "Direct link to Webhook events")

Webhook events are created following specific triggers. For instance, when an [asynchronous note generation](/2025-04-03/user/generate-note-async.md) is completed, a webhook event of type `generate_note_async.succeeded` or `generate_note_async.failed` is created.

Each webhook event will result in one (or multiple in case of retry after failure) network calls to each of the provided webhook integration URLs.

All webhook events calls share the same body schema, except for the `data` field that’s specific to the webhook event’s type. For instance the `data` field for `generate_note_async.succeeded` contains the generated note along with the suggested Dot Phrases. Here’s an example body (and notable headers) for a webhook event call:

```
method: POST

headers:

	"x-nabla-webhook-timestamp:": "2024-07-15T12:47:34.730Z"

    # notice that here we have two signatures because the same webhook has two different secrets (one main secret and one rotated old secret).

	"x-nabla-webhook-signature": "4f56f2191cc9dd7b70fbdb35a976b5bd768adb444a25e2dcf13e907fc983b521,a6025a96c87436160fcc774afbee58403a2752631366357af2445e94505db9f6"

	"Content-Type": "application/json"
```

```
{

  "id": "0cf0b04d-5bbe-47a9-9601-3dd037644f65",

  "created_at": "2024-07-15T12:47:34.380Z",

  "type": "generate_note_async.succeeded",

  "data": {

    "id": "fe309250-cf74-4f1e-8787-f8ad2826460d",

    "client_request_id": null,

    "status": "succeeded",

    "payload": {

      "note": {

        "title": "Headache, stomachache, sleeping problems, and bruises on arms",

        "sections": [

          {

            "key": "CHIEF_COMPLAINT",

            "title": "Chief complaint",

            "text": "- Persistent fatigue\n- Mild headaches on the right side"

          },

          {

            "key": "PAST_MEDICAL_HISTORY",

            "title": "Past medical history",

            "text": "- Hypertension\n- Elevated blood sugar levels"

          },

          {

            "key": "LAB_RESULTS",

            "title": "Lab results",

            "text": "- Blood sugar level: 1.4 g/L\n- LDL cholesterol: 2 g/L"

          }

        ]

      },

      "suggested_dot_phrases": []

    }

  }

}
```
