Hygraph 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 Hygraph.
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 libraries
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 @hygraph/utils
Once you've installed this in your project, you can use it like this:
const { verifyWebhookSignature } = require('@hygraph/utils');const secret = '...'; // This should be the same as set in Hygraphconst 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 manually
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 Hygraph projectt=
is the timestamp of the event
Step 1: Extract the signature and timestamp from the header
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 string
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 signature
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!
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
.
Blog Author
Jamie Barton
Jamie is a software engineer turned developer advocate. Born and bred in North East England, he loves learning and teaching others through video and written tutorials. Jamie currently publishes Weekly GraphQL Screencasts.