GraphCMS is pleased to announce its latest improvement to webhooks — signed webhooks.
From today, you can enable signed webhooks by adding a 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 GraphCMSconst body = {}; // Typically req.bodyconst 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 signatureenv=
is the environment of the GraphCMS projectt=
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
.