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
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
Create a new webhook in Webhooks from the API Admin Console
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
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 and RFC-4231.
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 are created following specific triggers. For instance, when an asynchronous note generation 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": []
}
}
}