Gareth Wilson Gareth Wilson

Guide to GitBook Webhooks: Features and Best Practices

Published


GitBook hosts product documentation, API references and internal knowledge bases, edited in a block editor or synced from Git. Its Webhook integration sends events out of GitBook when content changes or readers interact with a published site, which lets you rebuild a search index after an edit, stream page views into your own analytics, or route reader feedback to the team that owns the page.

Here we look at where the GitBook integration is installed, the three events it sends, how to verify X-GitBook-Signature, and what GitBook's short retry schedule means for your receiver.

What are GitBook webhooks?

Webhooks in GitBook are an integration rather than a workspace setting. You install the Webhook integration in your organisation, then into individual spaces or sites, and give each installation a webhook URL and a selection of event types.

Where you install it decides what you can subscribe to. A space installation reports content changes. A site installation reports reader activity: page views and page feedback. Every event carries the installationId it came from, so one receiver can serve several installations.

GitBook webhook features

FeatureDetails
Signaturehex HMAC-SHA256
Signature headerx-gitbook-signature
BodyJSON
Event routingBody field type

GitBook's Webhook integration guide also documents:

  • A signature header of t=<unix>,v1=<hex>, with the HMAC taken over the timestamp, a dot and the body.
  • Up to 3 retries with exponential backoff and jitter from a 1-second base, for network errors, 5xx and 429.
  • No retry for any other 4xx.

It does not state a response timeout, where the signing secret is shown, or any source IP addresses.

Events and payloads

EventMeaning
space_content_updatedTriggered when content in a space is modified.
site_viewTriggered when a user visits a page on your GitBook site.
page_feedbackTriggered when users provide feedback on pages.

All three bodies share eventId, type and installationId. A content update adds spaceId and revisionId, which you can pass to GitBook's API to fetch what changed. The two site events add siteId, a visitor object, the page url and the referrer, and page_feedback adds pageId, spaceId and a feedback object.

This is the page_feedback sample from the webhook samples library:

{
  "eventId": "evt_3456789012cdefgh",
  "type": "page_feedback",
  "siteId": "site_abc123",
  "spaceId": "space_xyz789",
  "installationId": "inst_def456",
  "pageId": "page_feedback123",
  "feedback": {
    "rating": "good",
    "comment": "This page was very helpful!"
  },
  "visitor": {
    "anonymousId": "anon_789ghi",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "ip": "192.168.1.101",
    "cookies": {}
  },
  "url": "https://docs.example.com/api-reference",
  "referrer": "https://docs.example.com/getting-started"
}

visitor includes the reader's IP address, user agent and cookies, so site_view and page_feedback events contain personal data. Decide what you store before you start collecting them.

Setting up GitBook webhooks

  1. Install the Webhook integration in your GitBook organisation.
  2. Install it into each space or site you want events from.
  3. Set the Webhook URL, for example https://example.com/webhooks/gitbook.
  4. Select the event types. Spaces offer content updates. Sites offer site views and page feedback.
  5. Find the signing secret for the installation and store it in your server's configuration. GitBook's samples read it from GITBOOK_SECRET. The guide doesn't say where the secret appears, so check the installation's configuration screen.

For Hookdeck, create a source with the GitBook type, use the installation's secret as its verification secret, and enter the source URL as the webhook URL. The Hookdeck CLI then forwards events to a local server:

npx hookdeck-cli listen 3000 gitbook --path /webhooks/gitbook

Editing a page in a space, or visiting the site, produces a test event.

Securing GitBook webhooks

Split X-GitBook-Signature on commas to get t and v1. Concatenate the t string, a dot and the raw body, compute a hex HMAC-SHA256 with the installation's secret, and compare it with v1 in constant time. GitBook's verification example uses the same timestamp.payload construction.

Node.js verification

const crypto = require('node:crypto');

function verifySignature(rawBody, headers, secret) {
  if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
  const signature = headers['x-gitbook-signature'];
  if (typeof signature !== 'string') return false;
  const key = Buffer.from(secret, 'utf8');
  const parts = signature.split(',').map((part) => part.trim());
  const times = parts.filter((part) => part.startsWith('t=')).map((part) => part.slice(2));
  if (times.length !== 1 || !/^[0-9]+$/.test(times[0])) return false;
  const message = Buffer.concat([Buffer.from(`${times[0]}.`, 'utf8'), rawBody]);
  const candidates = parts.filter((part) => part.startsWith('v1=')).map((part) => part.slice(3));
  const expected = crypto.createHmac('sha256', key).update(message).digest();
  return candidates.some((candidate) => {
    if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
    const received = Buffer.from(candidate, 'hex');
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });
}

The verifier sets no age limit on t. If you add one for a direct receiver, keep it generous enough to cover GitBook's retries.

The handler stores each event under its eventId and only then responds, so a retry finds the row already there:

const express = require('express');

const app = express();

const HANDLED_TYPES = new Set(['space_content_updated', 'site_view', 'page_feedback']);

app.post('/webhooks/gitbook', express.raw({ type: '*/*' }), async (req, res) => {
  const secret = process.env.GITBOOK_SECRET;
  if (!secret) return res.sendStatus(500);
  if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);

  let event;
  try {
    event = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }
  if (!event || typeof event !== 'object' || Array.isArray(event)) return res.sendStatus(400);
  try {
    if (!HANDLED_TYPES.has(event.type)) return res.sendStatus(200);

    const isNew = await recordGitBookEvent(event.eventId, event.installationId, event);
    if (isNew && event.type === 'space_content_updated') {
      await queueReindex(event.spaceId, event.revisionId);
    }
  } catch {
    return res.sendStatus(503);
  }

  res.sendStatus(200);
});

recordGitBookEvent returns false when the eventId already exists. GitBook's own example keeps seen IDs in memory for 2 minutes, which only works on a single process. A database table survives restarts and scales across instances.

Return 401 for a bad signature, since GitBook does not retry it, but never return 400 for a temporary problem on your side. A 400 is final, while a 500 or 429 gets a retry.

Hookdeck's GitBook source verifies the same timestamped HMAC without an age check. It replies 200 to GitBook and records a mismatch as failed verification.

Make GitBook webhooks production-ready. Hookdeck Event Gateway verifies each GitBook event, keeps it, and retries delivery to your indexer or analytics pipeline for far longer than GitBook's three attempts in about 7 seconds.

GitBook webhook limitations and pain points

Retries are over within seconds

The Problem: A restart or deploy of your receiver loses events.

Why It Happens: GitBook retries up to 3 times, with delays of roughly 1, 2 and 4 seconds. Any outage longer than that exhausts the schedule.

Workarounds:

  • Run more than one instance of the receiver so a deploy never takes it fully offline.
  • For content updates, periodically compare each space's current revision with the last one you processed.

How Hookdeck Can Help: Hookdeck accepts the event on GitBook's first attempt and retries your service on a schedule you choose, over minutes or hours.

Client errors are final

The Problem: A transient fault that your framework reports as 400, 404 or 413 drops the event permanently.

Why It Happens: GitBook treats every 4xx other than 429 as a client error and doesn't retry.

Workarounds:

  • Return 5xx or 429 for anything that might succeed later.
  • Make sure body size limits and routing are correct before going live.

How Hookdeck Can Help: Hookdeck receives GitBook's request regardless of your service's state, and its retry rules for your destination are independent of GitBook's.

Undocumented secret location

The Problem: It isn't obvious where to find the key that verifies signatures.

Why It Happens: GitBook's guide shows the X-GitBook-Signature format but doesn't say where the integration's secret is set or displayed.

Workarounds:

  • Look in the Webhook integration's configuration for the space or site.
  • Treat each installation as having its own secret until you've confirmed otherwise.

How Hookdeck Can Help: With a Hookdeck source per installation, each secret lives on its own source and failed verifications show up in the request log, which makes a wrong secret easy to spot.

Reader events carry personal data

The Problem: site_view and page_feedback include IP addresses, user agents and cookies.

Why It Happens: GitBook sends the visitor object as captured.

Workarounds:

  • Drop or hash visitor.ip and visitor.cookies before storage.
  • Subscribe to site views only if you have a use for them.

How Hookdeck Can Help: A Hookdeck transformation can strip the visitor fields before the event reaches your systems.

Best practices

Choose status codes deliberately

GitBook's retry decision depends entirely on your status code. Use 200 for success, 401 for a bad signature and 5xx for anything you'd like to see again.

Deduplicate durably on eventId

Every event has an eventId. Store it with a unique constraint, as described in the guide to implementing webhook idempotency.

Keep handlers fast

Reindexing a space or writing to a warehouse takes longer than a webhook should. Queue the work and respond, following the approach in why to process webhooks asynchronously.

Fetch content by revision

space_content_updated tells you which revision to look at, not what changed. Fetch the revision from the GitBook API in a worker and compare it with the previous one.

Conclusion

GitBook's Webhook integration gives you content updates from spaces and view and feedback events from sites, each signed over a timestamp and the body in X-GitBook-Signature. Verify against the raw bytes, store by eventId, and treat your status codes carefully, because GitBook's three retries end within seconds and a 4xx ends them at once.

Hookdeck Event Gateway holds every GitBook event until your services can accept it.

Get started with Hookdeck for free and handle GitBook webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.