Receiving webhook notifications

The GitBook Webhook Integration allows you to receive real-time notifications when events occur in your GitBook spaces. This integration supports configurable webhook URL, HMAC signature verification, and automatic retry logic with exponential backoff.

Features

  • Real-time Event Delivery: Receive instant notifications for selected events

  • HMAC Signature Verification: Secure webhook delivery with cryptographic verification

  • Automatic Retry Logic: Built-in retry mechanism with exponential backoff for failed deliveries

  • Configurable Events: Choose which events to receive (site views, content updates, page feedback)

Supported Events

The webhook integration can be installed either in Spaces or in Sites. The list of events you can select depends on where the integration is installed.

For spaces:

  • Content updates - When content in your space is modified

For sites:

  • Site views - When users visit pages on your site

  • Page feedback - When users provide feedback on pages

Content Updated Events (space_content_updated)

Triggered when content in a space is modified.

Payload Example:

{
  "eventId": "evt_2345678901bcdefg",
  "type": "space_content_updated",
  "spaceId": "space_xyz789",
  "installationId": "inst_def456",
  "revisionId": "rev_abc123def456"
}

Site View Events (site_view)

Triggered when a user visits a page on your GitBook site.

Payload Example:

{
  "eventId": "evt_1234567890abcdef",
  "type": "site_view",
  "siteId": "site_abc123",
  "installationId": "inst_def456",
  "visitor": {
    "anonymousId": "anon_789ghi",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
    "ip": "192.168.1.100",
    "cookies": {
      "session_id": "sess_xyz789"
    }
  },
  "url": "https://docs.example.com/getting-started",
  "referrer": "https://www.google.com/search?q=example+docs"
}

Page Feedback Events (page_feedback)

Triggered when users provide feedback on pages.

Payload Example:

{
  "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"
}

Configuration

Required Settings

  • Webhook URL: The endpoint where events will be sent

  • Event Types: Select which events to receive

Webhook Security

HMAC Signature Verification

All webhook requests include an HMAC-SHA256 signature in the X-GitBook-Signature header for verification.

Header Format:

X-GitBook-Signature: t=1640995200,v1=abc123def456...

Where:

  • t: Unix timestamp of the request

  • v1: HMAC-SHA256 signature of the payload

Signature Verification Example

const crypto = require('crypto');

function verifyGitBookSignature(payload, signature, secret) {
  if (!signature) return false;
  
  try {
    // Parse signature format: t=timestamp,v1=hash
    const parts = signature.split(',');
    let timestamp, hash;
    
    for (const part of parts) {
      if (part.startsWith('t=')) {
        timestamp = part.substring(2);
      } else if (part.startsWith('v1=')) {
        hash = part.substring(3);
      }
    }
    
    if (!timestamp || !hash) return false;
    
    // Generate expected signature (our implementation uses timestamp.payload format)
    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(`${timestamp}.${payload}`)
      .digest('hex');
    
    // Constant-time comparison to prevent timing attacks
    return crypto.timingSafeEqual(
      Buffer.from(hash, 'hex'),
      Buffer.from(expectedSignature, 'hex')
    );
  } catch (error) {
    return false;
  }
}

// Usage
const isValid = verifyGitBookSignature(
  requestBody,
  request.headers['x-gitbook-signature'],
  'your-secret-key'
);

Retry Logic

The integration includes automatic retry logic for failed webhook deliveries:

  • Max Retries: 3 attempts

  • Backoff Strategy: Exponential backoff with jitter

  • Base Delay: 1 second

  • Jitter: ±10% of base delay

  • Retry Conditions:

    • Network errors (timeouts, connection refused)

    • Server errors (5xx status codes)

    • Rate limiting (429 status codes)

  • No Retry: Client errors (4xx except 429)

Retry Schedule Example

Attempt
Base Delay
Jitter Range
Total Delay Range
Schedule

1

1s

±0.1s

1.0-1.1s

1s

2

2s

±0.2s

2.0-2.2s

2s

3

4s

±0.4s

4.0-4.4s

4s

Error Handling

HTTP Status Codes

  • 200: Success

  • 400: Bad Request (client error, no retry)

  • 429: Too Many Requests (rate limited, will retry)

  • 500: Internal Server Error (server error, will retry)

Best Practices

1. Webhook Endpoint Design

const express = require('express');
const crypto = require('crypto');
const app = express();

// Express.js example
app.post('/webhooks/gitbook', express.raw({type: 'application/json'}), (req, res) => {
  // Get raw body as string for signature verification
  const requestBody = req.body.toString();
  
  // Verify signature first
  const signature = req.headers['x-gitbook-signature'];
  const isValid = verifyGitBookSignature(requestBody, signature, process.env.GITBOOK_SECRET);
  
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Parse and process event
  const event = JSON.parse(requestBody);
  
  switch (event.type) {
    case 'site_view':
      handleSiteView(event);
      break;
    case 'space_content_updated':
      handleContentUpdate(event);
      break;
    case 'page_feedback':
      handlePageFeedback(event);
      break;
  }
  
  res.status(200).json({ received: true });
});

2. Idempotency

Handle duplicate events gracefully:

const express = require('express');
const crypto = require('crypto');
const app = express();

const processedEvents = new Map();
const EVENT_RETENTION_MS = 2 * 60 * 1000; // 2 minutes

function remember(eventId) {
  if (processedEvents.has(eventId)) return false;

  // Insert and schedule automatic eviction
  const timer = setTimeout(() => {
    processedEvents.delete(eventId);
  }, EVENT_RETENTION_MS);

  processedEvents.set(eventId, timer);
  return true;
}

function handleEvent(event) {
  // Guard goes first so concurrent deliveries don’t double-process
  if (!remember(event.eventId)) {
    console.log('Duplicate event ignored:', event.eventId);
    return;
  }

  // Process event...
  // doWork(event);

  // If processing fails and you want to allow a retry, you can undo the remember:
  // clearTimeout(processedEvents.get(event.eventId));
  // processedEvents.delete(event.eventId);
}

3. Async Processing

Process events asynchronously to respond quickly:

const express = require('express');
const crypto = require('crypto');
const app = express();

app.post('/webhooks/gitbook', express.raw({type: 'application/json'}), (req, res) => {
  // Get raw body as string for signature verification
  const requestBody = req.body.toString();
  
  // Verify signature
  const signature = req.headers['x-gitbook-signature'];
  const isValid = verifyGitBookSignature(requestBody, signature, process.env.GITBOOK_SECRET);
  
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Respond immediately
  res.status(200).json({ received: true });
  
  // Process asynchronously
  setImmediate(() => {
    const event = JSON.parse(requestBody);
    processEvent(event);
  });
});

Last updated

Was this helpful?