# @sanity/webhook

> Toolkit for dealing with GROQ-powered webhooks delivered by Sanity.io

Latest version **4.0.4** (published 2024-04-11) · MIT license · 0 weekly downloads

## Install

```sh
npm install @sanity/webhook
pnpm add @sanity/webhook
yarn add @sanity/webhook
bun add @sanity/webhook
```

## Health

**Score 50/100 (C)** — status: abandoned.

Positive: has types; esm support; no vulnerabilities; has provenance; high maintenance score; high quality score.

Warnings: low downloads.

Negative: abandoned.

## Facts

| | |
|---|---|
| Version | 4.0.4 |
| Published | 2024-04-11 |
| First published | 2021-10-06 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=20.0.0 |
| Dependencies | 0 |
| Unpacked size | 59.9 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 44 |
| Author | Sanity.io |
| Maintainers | jtpetty, drewsanity, refiito, sergeisarviro, ash, indrek.karner, cngonzalez-sanity, rdunk, rneatherway-sanity, ricokahler, pedro-sanity, jonabc, kenjonespizza, pauloborgesf, binoy14, simen.svale, svirs, josh_sanity_io, joneidejohnsen, nina.andal, rankers, snorreeb, mattcraig, vincentquigley, stipsan, michael-sanity, rubioz, tonina, ritasdias, simeonsanity, kmelve, bjoerge, rexxars, skogsmaskin, robinpyon, mariuslundgard, sanity-io, evenw, radhe_sanity, rbotten, judofyr, obliadp, dcilke, fredcarlsen, hermanw, sgulseth, atombender |
| Keywords | webhook, webhooks, sanity-io, sanity, verify, validate |

## Links

- npm: https://www.npmjs.com/package/@sanity/webhook
- Repository: https://github.com/sanity-io/webhook-toolkit
- Homepage: https://github.com/sanity-io/webhook-toolkit#readme
- Issues: https://github.com/sanity-io/webhook-toolkit/issues
- npm.io page: https://npm.io/package/@sanity/webhook

## Alternatives

- [@sindresorhus/slugify](https://npm.io/package/@sindresorhus/slugify.md) — 3.7M weekly downloads
- [solid-js](https://npm.io/package/solid-js.md) — 2.7M weekly downloads
- [expo-glass-effect](https://npm.io/package/expo-glass-effect.md) — 2.5M weekly downloads
- [nanoassert](https://npm.io/package/nanoassert.md) — 780.8K weekly downloads
- [@ffmpeg/ffmpeg](https://npm.io/package/@ffmpeg/ffmpeg.md) — 529.5K weekly downloads

## Recent versions

- 4.0.4 (latest) — 2024-04-11
- 4.0.2-bc (bc) — 2024-02-27
- 4.0.3 — 2024-03-18
- 4.0.2 — 2024-02-26
- 4.0.1 — 2024-01-25
- 4.0.0 — 2023-11-27
- 3.0.1 — 2023-08-19
- 3.0.0 — 2023-08-18
- 2.0.0 — 2022-06-29
- 1.1.0 — 2022-06-29
- 1.0.2 — 2021-10-07
- 1.0.1 — 2021-10-06
- 1.0.0 — 2021-10-06

## README

# @sanity/webhook

Toolkit for dealing with [GROQ-powered webhooks](https://www.sanity.io/docs/webhooks) delivered by [Sanity.io](https://www.sanity.io/).

## Installing

```sh
$ npm install @sanity/webhook
```

## Usage

```js
// ESM / TypeScript
import {isValidSignature} from '@sanity/webhook'

// CommonJS
const {isValidSignature} = require('@sanity/webhook')
```

### Usage with Express.js (or similar)

```ts
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) {
      // Note that `req.body` is now a parsed version, set `parseBody` to `false`
      // if you want the raw text version of the request body
    },
  )
  .listen(1337)
```

### Usage with Next.js

```ts
// pages/api/hook.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) // Read the body into a string
  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})
}

// Next.js will by default parse the body, which can lead to invalid signatures
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](https://github.com/expressjs/body-parser#bodyparsertextoptions) middleware registered for the route (with `{type: 'application/json'}`).

### Functions

- [requireSignedRequest](README.md#requiresignedrequest)
- [assertValidSignature](README.md#assertvalidsignature)
- [isValidSignature](README.md#isvalidsignature)
- [assertValidRequest](README.md#assertvalidrequest)
- [isValidRequest](README.md#isvalidrequest)

### 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<void>_

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<boolean>_

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<void>_

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<boolean>_

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](https://developer.mozilla.org/en-US/docs/Web/API/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:

- [Express.js or similar](#usage-with-expressjs-or-similar)
- [Next.js](#usage-with-nextjs)

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.

---
_Source: https://npm.io/package/@sanity/webhook · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
