Webhooks

Send annotation events to external services automatically

Overview

Webhooks allow you to receive annotation data at external URLs when users interact with annotations. This enables integrations with Slack, Discord, custom backends, CI/CD pipelines, and more.

Configure a webhook URL via the webhookUrl prop, and events will be sent automatically when annotations are created, updated, deleted, or submitted.

Browser
Agentation
Webhook
Server
Service
Slack, GitHub...
annotation.addPOST /slack200 OKsubmitPOST /github/issues201 Created
request
response

Configuration

Add the webhookUrl prop to enable webhooks:

import { Agentation } from "agentation";


function App() {
  return (
    <>
      <YourApp />
      <Agentation webhookUrl="https://your-server.com/webhook" />
    </>
  );
}

With a webhook URL configured, you have two options: enable Auto-Send to fire events automatically, or click "Send Annotations" in the toolbar manually. When Auto-Send is on, the toolbar button is hidden.

Events

Webhooks fire for the following events:

  • annotation.add: New annotation created
  • annotation.delete: Annotation deleted
  • annotation.update: Annotation comment edited
  • annotations.clear: All annotations cleared
  • submit: "Send Annotations" clicked

Webhook Payload

All events send a POST request with the following JSON structure:

{
  "event": "annotation.add",
  "timestamp": 1706234567890,
  "url": "https://example.com/dashboard",
  "annotation": {
    "id": "1706234567890",
    "comment": "Button is cut off on mobile",
    "element": "button",
    "elementPath": "body > main > form > button.submit-btn",
    "timestamp": 1706234567890
  }
}

Event-specific payloads

annotation.add / annotation.delete / annotation.update

{
  "event": "annotation.add",
  "timestamp": 1706234567890,
  "url": "https://example.com/page",
  "annotation": { ... }
}

annotations.clear

{
  "event": "annotations.clear",
  "timestamp": 1706234567890,
  "url": "https://example.com/page",
  "annotations": [ ... ]
}

submit

{
  "event": "submit",
  "timestamp": 1706234567890,
  "url": "https://example.com/page",
  "output": "# Page Feedback\n\n...",
  "annotations": [ ... ]
}

Combining with Callbacks

You can use webhooks alongside the onSubmit and other callback props. Both will fire when events occur:

<Agentation
  webhookUrl="https://your-server.com/webhook"
  onSubmit={(output, annotations) => {
    // This fires in addition to the webhook
    console.log("Submitted:", annotations.length, "annotations");
  }}
  onAnnotationAdd={(annotation) => {
    // Track in analytics
    analytics.track("annotation_created");
  }}
/>

Use Cases

Slack Notifications

// Server webhook handler
app.post("/webhook/agentation", async (req, res) => {
  const { event, annotation, url } = req.body;


  if (event === "annotation.add") {
    await fetch(process.env.SLACK_WEBHOOK_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        text: `New annotation on ${url}: "${annotation.comment}"`,
      }),
    });
  }


  res.json({ ok: true });
});

GitHub Issue Creation

app.post("/webhook/agentation", async (req, res) => {
  const { event, output, annotations } = req.body;


  if (event === "submit" && annotations.length > 0) {
    await fetch("https://api.github.com/repos/owner/repo/issues", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: `[Feedback] ${annotations.length} annotation(s)`,
        body: output,
        labels: ["feedback"],
      }),
    });
  }


  res.json({ ok: true });
});

Real-time Dashboard

// Server with WebSocket broadcast
app.post("/webhook/agentation", (req, res) => {
  const { event, annotation, url } = req.body;


  // Broadcast to connected dashboard clients
  wss.clients.forEach((client) => {
    client.send(JSON.stringify({
      type: "annotation_event",
      event,
      annotation,
      url,
    }));
  });


  res.json({ ok: true });
});

Security considerations

  • Use HTTPS: Always use encrypted connections for webhook URLs
  • Validate origin: Check the request origin if your webhook is public
  • Rate limiting: Implement rate limits to prevent abuse
  • Sanitize content: Annotation comments may contain user-generated content; sanitize before rendering

Testing Webhooks

Tools for testing webhooks during development:

  • webhook.site: Free public endpoint for testing payloads
  • ngrok: Expose local server for testing with real URLs
  • RequestBin: Inspect and debug webhook payloads

Quick Test Setup

// Use webhook.site for testing
<Agentation webhookUrl="https://webhook.site/your-unique-id" />


// Then create annotations and check webhook.site for payloads

Server delivery retries

The MCP server sends webhooks in the background. Network errors, timeouts, HTTP 429 and 5xx responses are retried; other unsuccessful responses stop immediately. Browser-configured webhook delivery is separate from this server retry queue.

AGENTATION_WEBHOOK_MAX_RETRIES=3
AGENTATION_WEBHOOK_BASE_DELAY_MS=1000
AGENTATION_WEBHOOK_MAX_DELAY_MS=30000
AGENTATION_WEBHOOK_TIMEOUT_MS=10000

These are the defaults. Set AGENTATION_WEBHOOK_MAX_RETRIES=0 to disable retries. Backoff includes jitter; Retry-After is honored within the maximum delay. Receivers should deduplicate using X-Agentation-Delivery-Id, which stays the same across attempts.

The queue is held in memory. Stopping the server cancels pending deliveries; delivery is neither durable nor guaranteed exactly once.