@sanity/webhook
Toolkit for dealing with GROQ-powered webhooks delivered by Sanity.io.
Installing
$ npm install @sanity/webhook
Usage
import {isValidSignature} from '@sanity/webhook'
const {isValidSignature} = require('@sanity/webhook')
Usage with Express.js (or similar)
import express from 'express'
import bodyParser from 'body-parser'
import {requireSignedRequest} from '@sanity/webhook'
express()
.use(bodyParser.text({type: 'application/json'}))
.post(
'/hook',
requireSignedRequest({secret: process.env.MY_WEBHOOK_SECRET, parseBody: true}),
function myRequestHandler(req, res) {
},
)
.listen(1337)
Usage with Next.js
import {isValidSignature, SIGNATURE_HEADER_NAME} from '@sanity/webhook'
const secret = process.env.MY_WEBHOOK_SECRET
export default async function handler(req, res) {
const signature = req.headers[SIGNATURE_HEADER_NAME]
const body = await readBody(req)
if (!(await isValidSignature(body, signature, secret))) {
res.status(401).json({success: false, message: 'Invalid signature'})
return
}
const jsonBody = JSON.parse(body)
doSomeMagicWithPayload(jsonBody)
res.json({success: true})
}
export const config = {
api: {
bodyParser: false,
},
}
async function readBody(readable) {
const chunks = []
for await (const chunk of readable) {
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk)
}
return Buffer.concat(chunks).toString('utf8')
}
Documentation
Note that the functions requireSignedRequest
, assertValidRequest
and isValidRequest
all require that the request object should have a text body
property.
E.g. if you're using Express.js or Connect, make sure you have a Text body-parser middleware registered for the route (with {type: 'application/json'}
).
Functions
requireSignedRequest
requireSignedRequest(options
: SignatureMiddlewareOptions): RequestHandler
Returns an Express.js/Connect-compatible middleware which validates incoming requests to ensure they are correctly signed.
This middleware will also parse the request body into JSON: The next handler will have req.body
parsed into a plain JavaScript object.
Options:
secret
(string, required) - the secret to use for validating the request.parseBody
(boolean, optional, default: true) - whether or not to parse the body as JSON and set request.body
to the parsed value.respondOnError
(boolean, optional, default: true) - whether or not the request should automatically respond to the request with an error, or (if false
) pass the error on to the next registered error middleware.
assertValidSignature
assertValidSignature(stringifiedPayload
: string, signature
: string, secret
: string): Promise
Asserts that the given payload and signature matches and is valid, given the specified secret. If it is not valid, the function will throw an error with a descriptive message
property.
isValidSignature
isValidSignature(stringifiedPayload
: string, signature
: string, secret
: string): Promise
Returns whether or not the given payload and signature matches and is valid, given the specified secret. On invalid, missing or mishaped signatures, this function will return false
instead of throwing.
assertValidRequest
assertValidRequest(request
: ConnectLikeRequest, secret
: string): Promise
Asserts that the given request has a request body which matches the received signature, and that the signature is valid given the specified secret. If it is not valid, the function will throw an error with a descriptive message
property.
isValidRequest
isValidRequest(request
: ConnectLikeRequest, secret
: string): Promise
Returns whether or not the given request has a request body which matches the received signature, and that the signature is valid given the specified secret.
Migration
From version 3.x to 4.x
In versions 3.x and below, this library would syncronously assert/return boolean values. From v4.0.0 and up, we now return promises instead. This allows using the Web Crypto API, available in a broader range of environments.
v4 also requires Node.js 18 or higher.
From parsed to unparsed body
In versions 1.0.2 and below, this library would accept a parsed request body as the input for requireSignedRequest()
, assertValidRequest()
and isValidRequest()
.
These methods would internally call JSON.stringify()
on the body in these cases, then compare it to the signature. This works in most cases, but because of slightly different JSON-encoding behavior between environments, it could sometimes lead to a mismatch in signatures.
To prevent these situations from occuring, we now highly recommend that you aquire the raw request body when using these methods.
See the usage examples further up for how to do this:
Differences in behavior:
- In version 2.0.0 and above, an error will be thrown if the request body is not a string or a buffer.
- In version 1.1.0, a warning will be printed to the console if the request body is not a string or buffer.
License
MIT-licensed. See LICENSE.