Introducing Signed Webhooks

Go further with securing webhooks by signing them with a secret key.

Jamie Barton
Jamie Barton

GraphCMS is pleased to announce its latest improvement to webhooks — signed webhooks.

From today, you can enable signed webhooks by adding a secret key:

Webhook secret key

Once a webhook is triggered, your endpoint will receive the usual payload (if included), and a new gcms-signature header that can be used to verify it came from GraphCMS.

The header gcms-signature looks something like:

sign=x0jU8z7AXAARIDBgsiVyfOG000wb2HhqN/mxl6+RSMk=, env=master, t=1631270481036

Now you can generate your own signature using the SHA256 algorithm with the shared secret key you added to the webhook configuration, and verify it matches the same once sent in the request header gcms-signature.

Verify signatures using our official librariesAnchor

To make things easier for developers working with Node, we've released a small utility that will construct a new signature for you, with your values.

npm install @graphcms/utils

Once you've installed this in your project, you can use it like this:

const { verifyWebhookSignature } = require('@graphcms/utils');
const secret = '...'; // This should be the same as set in GraphCMS
const body = {}; // Typically req.body
const signature = '...'; // Typically req.headers['gcms-signature']
const isValid = verifyWebhookSignature({ body, signature, secret });

You'll need the request body and headers to pass to verifyWebhookSignature.

If isValid is truthy then you can safely execute your webhook handler code knowing the request is genuine, otherwise you should abort any further action.

Verify signatures manuallyAnchor

You may also verify webhook signatures manually by generating your own signature using whatever cryptographic library can generate a SHA256 digest.

Let's break the gcms-signature header down:

sign=x0jU8z7AXAARIDBgsiVyfOG000wb2HhqN/mxl6+RSMk=, env=master, t=1631270481036
  • sign= is the signature
  • env= is the environment of the GraphCMS project
  • t= is the timestamp of the event

Step 1: Extract the signature and timestamp from the headerAnchor

First you'll need to get the signature, and timestamp from the header so they can be used to construct a new payload. If you're using JavaScript, it could look something like this:

const [rawSign, rawEnv, rawTimestamp] = signature.split(", ");
const sign = rawSign.replace("sign=", "");
const EnvironmentName = rawEnv.replace("env=", "");
const Timestamp = parseInt(rawTimestamp.replace("t=", ""));

Step 2: Prepare the payload stringAnchor

You'll next need to create a string of the payload that will be hashed, using the request body. If you're using JavaScript, it could look something like:

let payload = JSON.stringify({
Body: JSON.stringify(body),
EnvironmentName,
TimeStamp: Timestamp,
});

Step 3: Generate the signatureAnchor

If you're using JavaScript, it could look something like:

const { createHmac } = require("crypto");
const hash = createHmac("sha256", secret).update(payload).digest("base64");

Step 4: Compare the signatures match!Anchor

All that's left to do is compare that the sign= value and hash match. If you're using JavaScript, this may look something like:

const isValid = sign === hash

That's it! You can then decide whether or not you want to continue executing the webhook code based on the result of isValid.

Learn more about validating webhooks.

It's Easy To Get Started

GraphCMS plans are flexibly suited to accommodate your growth. Get started for free, or request a demo to discuss larger projects with more complex needs