Webhook Security

In order to integrate with any webhook implementation, be that Claims Manager or any other webhook provider, you first need to expose a publically facing endpoint on the internet in order to receive the webhooks. Ensuring that these communications are secure is vital. Let's look at the steps you can take to protect your endpoint

HTTPS/TLS

As with any activity on the internet, it is strongly recommended to secure this traffic by ensuring that your endpoint is using HTTPS to encrypt the data as it is sent over the wire. In our live envrionment, we will not accept a webhook endpoint that doesn't use HTTPS. To support partners who may not have SSL certificates installed in lower environments, our test environment will accept a plain HTTP endpoint, but be aware that the live environment must use an SSL certificate from a trusted certificate provider, self-signed certificates will not be accepted.

Authenticate

When setting up your webhook endpoint in Claims Manager, you can add a header to the payload that we will send you. This can include a unique API Key that only you know so you can be more confident that the request came from us. This header name and value can be anything you like (within the standard limitations of HTTP Headers). The standard prefix for user defined headers is to prefix them with X- so you may want to call your header X-MyCompany-APIKey for example. Then when processing the request, you can look for this header and check the value matches what your are expecting.

Webhook Signing

Claims Manager signs all webhooks sent out to protect the data from being tampered with, or resent at a later date in a replay attack. To protect your application, it is recommended that you validate this signature before responding to any request. Given that the signature also includes a timestamp, an attacker is unable to change the timestamp without invalidating the signature. Your application can then use this timestamp to check that the webhook being sent is not too old; we suggest a tolerance of 5 minutes is enough to mitigate any delay on our side sending the request.

If you are validating the timestamp in the signature, be sure that your server's clock is accurate by using the Network Time Protocol to ensure that you are in sync with Claims Manager servers.

Each webhook sent from Claims Manager will include a X-Crawford-Signature header in the HTTP message headers. In order to validate that the signature is correct, you will also need the clientId that you use to authenticate to the Claims Manager API. The signature is made up of the unix timestamp (the number of seconds since the 1st of January 1970 at UTC), a colon and then the computed HMAC using the SHA256 hash function using your clientId as the key.

The steps you need to take to regenerate the signature are as follows:

  1. Locate the X-Crawford-Signature header in the request message and split the value on the colon to leave you with the first string, which is the timestamp and the second string which is the signature.
  2. Concatenate the timestamp, the character . and the raw JSON payload of the webhook into one string to compute the HMAC with.
  3. Compute an HMAC with the SHA256 hash function using your clientId as the key. Compare this generated signature with the one extracted from the header and ensure that they match.

Note that you require the raw json body of the request to correctly perform the signature validation. Any manipulation of the formatting or line endings, for example, may cause vlaidation to fail.

Let's walk through an example. Let's assume your clientId is abcde123456 and you received the following webhook:

HTTP POST 1.1

X-Crawford-Signature: "1492774577:2739262ab5f97fed7537e6b6ed2a48eb3e50d49f6c708ae5fc536f1d9719f61f"

{
   "event":"Incident Status",
   "action":"Updated",
   "incidentId":6904165,
   "resource":"/incidents/6904165",
   "createdDateTime":"2023-12-28T21:24:52.410",
   "data":{
      "newStatus":"Closed",
      "previousStatus":"Open"
   }
}

First we split the X-Crawford-Signature header into the timestamp (1492774577) and the signature to validate (2739262ab5f97fed7537e6b6ed2a48eb3e50d49f6c708ae5fc536f1d9719f61f). Next we concatenate the timestamp, the character . and the raw JSON request body to get the following string:

1492774577.{
   "event":"Incident Status",
   "action":"Updated",
   "incidentId":6904165,
   "resource":"/incidents/6904165",
   "createdDateTime":"2023-12-28T21:24:52.410",
   "data":{
      "newStatus":"Closed",
      "previousStatus":"Open"
   }
}

We then use this string to compute the HMAC using the SHA256 hash function and our clientId (abcde123456) as the key. This gives us a value of 2739262ab5f97fed7537e6b6ed2a48eb3e50d49f6c708ae5fc536f1d9719f61f which we can verify is the same value as the second half of the X-Crawford-Signature header value. Below is an example implementation using node.js.

const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();

const clientId = '**Replace with your clientId**';

function verifySignature(signature, payload) {
    const hmac = crypto.createHmac('sha256', clientId);	
    const signatureParts = signature.split(":");
    const timestamp = signatureParts[0];
    const signatureHash = signatureParts[1];
    const preparedPayload = timestamp.concat(".", Buffer.from(payload, 'utf-8'));	
    const data = hmac.update(preparedPayload);
    const gen_hmac = data.digest('hex');

    return signatureHash === gen_hmac;
}	

app.post('/api/webhook', bodyParser.raw({inflate:true, limit: '100kb', type: 'application/json'}), (request, response) => {
  const event = request.body;

  if (verifySignature(request.get("X-Crawford-Signature"), request.body)) 
    response.json({received: true});
  else 
    response.send(401, "unauthorised");

});

app.listen(8000, () => console.log('Running on port 8000'));