Webhooks

In order to start listening to for webhooks sent from CaliberX, you will need to configure your webhook endpoints.

Adding an endpoint is as simple as providing a URL that you control and a list of event types that you want to listen to.

You can do this via your portal, under 'Developer Center'. Here you will see a panel including any existing webhook endpoints and you'll be able to add new endpoints.

When configuring your endpoint, make sure to take a look at the 'Event Catalog' to see the full descriptions of each endpoint as well as all the fields they send.

If you don't specify any event types, by default, your endpoint will receive all events, regardless of type. This can be helpful for getting started and for testing, but we recommend changing this to a subset later on to avoid receiving unexpected messages.

Verifying Webhooks

Webhooks should always be verified to ensure you are not vulnerable to impersonation attacks or replay attacks.

To ensure this each webhook call includes three headers with additional information that are used for verification:

  • webhook-id: the unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is being resent (e.g. due to a previous failure).
  • webhook-timestamp: timestamp in seconds since epoch.
  • webhook-signature: the Base64 encoded list of signatures (space delimited).

Constructing the signed content

The content to sign is composed by concatenating the id, timestamp and payload, separated by the full-stop character .. In code, it will look something like:

signedContent = "${webhook_id}.${webhook_timestamp}.${body}"

Where body is the raw body of the request. The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should not change the body in any way before verifying.

Determining the expected signature

HMAC with SHA-256 to sign webhooks.

So to calculate the expected signature, you should HMAC the signedContent from above using the base64 portion of your signing secret as the key.

For example, this is how you can calculate the signature in Node.js:

const crypto = require('crypto');

signedContent = `${webhook_id}.${webhook_timestamp}.${body}`  
const secret = "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH";

// Need to base64 decode the secret  
const secretBytes = new Buffer(secret.split('\_')[1], "base64");  
const signature = crypto  
  .createHmac('sha256', secretBytes)  
  .update(signedContent)  
  .digest('base64');  
console.log(signature);

This generated signature should match one of the ones sent in the webhook-signature header.

The webhook-signature header is composed of a list of space delimited signatures and their corresponding version identifiers. The signature list is most commonly of length one. Though there could be any number of signatures. For example:

v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=

Make sure to remove the version prefix and delimiter (e.g. v1,) before verifying the signature.

Please note that to compare the signatures it's recommended to use a constant-time string comparison method in order to prevent timing attacks.

Verify timestamp

The timestamp of the attempt is also sent in the webhook-timestamp header. You should compare this timestamp against your system timestamp and make sure it's within your tolerance in order to prevent timestamp attacks.