# Receive webhook notifications

The [Webhook integration](https://www.gitbook.com/integrations/webhook) 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)

### Getting started

Before following the rest of the guide, make sure the [Webhook integration](https://www.gitbook.com/integrations/webhook) is installed into your organization.&#x20;

### 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:**

```json
{
  "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:**

```json
{
  "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:**

```json
{
  "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

```javascript
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

```javascript
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:

```javascript
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:

```javascript
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);
  });
});
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gitbook.com/docs/developers/integrations/guides/webhook.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
