Webhooks

Get notified in real time when documentation changes.

Overview

Webhooks let you receive HTTP notifications when events happen in your workspace. Instead of polling the API for changes, AgentDocs will send a POST request to your specified URL whenever a page is created, updated, or deleted.

Webhooks are configured at the workspace level. A single webhook receives events for all pages across all spaces in the workspace.

ProThe Free plan includes up to 3 webhooks per workspace.Upgrade to Pro for unlimited webhooks with advanced features.

How it works
1. User edits a page in AgentDocs
2. AgentDocs saves the change
3. AgentDocs sends a POST to your webhook URL
4. Your server processes the event (e.g., update agent context, trigger rebuild)

Events

AgentDocs sends webhooks for the following events:

EventDescriptionTrigger
page.createdA new page was createdUI create, API POST
page.updatedA page's content or title was modifiedUI save, API PUT
page.deletedA page was deletedUI delete, API DELETE

Payload Format

Webhook payloads are sent as JSON POST requests with the following structure:

page.created / page.updated

POST https://your-server.com/webhook
Content-Type: application/json
X-AgentDocs-Signature: sha256=a1b2c3d4e5f6...
X-AgentDocs-Event: page.updated

{
  "event": "page.updated",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "workspace_id": "ws_abc123",
  "data": {
    "page_id": "pg_def456",
    "title": "Deployment Guide",
    "slug": "deployment-guide",
    "space_id": "sp_xyz789",
    "space_name": "Engineering Wiki",
    "updated_by": "alice@example.com",
    "content_preview": "# Deployment Guide

This guide covers..."
  }
}

page.deleted

{
  "event": "page.deleted",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "workspace_id": "ws_abc123",
  "data": {
    "page_id": "pg_def456",
    "title": "Deployment Guide",
    "space_id": "sp_xyz789",
    "deleted_by": "alice@example.com"
  }
}

HMAC Signature Verification

Every webhook request includes an X-AgentDocs-Signature header containing an HMAC-SHA256 signature of the request body. This lets you verify that the webhook was actually sent by AgentDocs, not a malicious third party.

Verification Process

  1. Extract the signature from the X-AgentDocs-Signature header
  2. Compute the HMAC-SHA256 of the raw request body using your webhook secret
  3. Compare the computed signature with the one in the header
  4. Reject the request if they don't match

Node.js Example

const crypto = require('crypto');

function verifyWebhookSignature(body, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// Express middleware
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-agentdocs-signature'];
  const isValid = verifyWebhookSignature(
    req.body.toString(),
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  console.log('Received event:', event.event, event.data.title);

  // Process the event...
  res.status(200).send('OK');
});

Python Example

import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = os.environ['WEBHOOK_SECRET']

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-AgentDocs-Signature', '')
    body = request.get_data(as_text=True)

    expected = 'sha256=' + hmac.new(
        WEBHOOK_SECRET.encode(),
        body.encode(),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        abort(401)

    event = request.get_json()
    print(f"Event: {event['event']} - {event['data']['title']}")

    return 'OK', 200

Retry Behavior

If your webhook endpoint doesn't respond with a 2xx status code within 10 seconds, AgentDocs will retry the delivery with exponential backoff:

AttemptDelayTotal Elapsed
1st retry10 seconds~10s
2nd retry30 seconds~40s
3rd retry2 minutes~2.5 min
4th retry (final)10 minutes~12.5 min

After 4 failed retries, the event is dropped. If your webhook consistently fails, it may be automatically disabled after a threshold of consecutive failures.

⚠️ Tip: Your webhook endpoint should respond quickly (within a few seconds). If you need to do heavy processing, accept the webhook immediately with a 200 response and process the event asynchronously via a background job queue.

Setting Up Webhooks

Via the UI

  1. Navigate to your workspace settings (/:workspaceSlug/settings)
  2. Scroll to the Webhooks section
  3. Click "Add Webhook"
  4. Enter your endpoint URL (must be HTTPS in production)
  5. A webhook secret will be generated automatically — save it securely
  6. Select which events to subscribe to (or leave all enabled)
  7. Click "Save"

Via the API

# Create a webhook
curl -X POST https://agentdocs.eu/api/workspaces/WORKSPACE_ID/webhooks \
  -H "Authorization: Token YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhook",
    "events": ["page.created", "page.updated", "page.deleted"]
  }'

# Response:
{
  "webhook": {
    "id": "wh_abc123",
    "url": "https://your-server.com/webhook",
    "secret": "whsec_a1b2c3d4e5f6...",
    "events": ["page.created", "page.updated", "page.deleted"],
    "active": true,
    "created_at": "2025-01-15T10:30:00.000Z"
  }
}

# ⚠️ Save the 'secret' value! It's only shown once at creation time.

Use Case: Keeping Agents in Sync

A common pattern is to combine webhooks with the raw endpoint to keep an agent's context always up to date:

// When a page.updated webhook fires:
async function handlePageUpdate(event) {
  const { page_id, title } = event.data;

  // Fetch the latest content via the raw endpoint
  const shareToken = await getShareTokenForPage(page_id);
  const response = await fetch(
    `https://agentdocs.eu/api/shared/${shareToken}/raw`
  );
  const markdown = await response.text();

  // Update your agent's context / vector store / cache
  await updateAgentContext(page_id, {
    title,
    content: markdown,
    updatedAt: event.timestamp,
  });

  console.log(`Updated agent context for: ${title}`);
}
AgentDocs v1.1.0 · 1814d7a