Glean Webhooks & Events

SkillDev tools

'Implement event-driven Glean indexing triggered by source system webhooks

Use Glean Webhooks & Events in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add Glean Webhooks & Events and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the Glean Webhooks & Events skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Glean Webhooks & EventsStart free

What this skill tells your AI

The instructions your AI receives, as published by jeremylongshore/tons-of-skills-marketplace in skills/.curated/glean-webhooks-events/SKILL.md and read by Ahel’s review.

Overview

Glean uses an event-driven indexing model where source system webhooks trigger incremental updates to the Glean Indexing API. Instead of emitting its own webhooks, Glean receives document changes from platforms like GitHub, Confluence, and Notion. You can also monitor internal Glean events such as document indexing completion, permission changes, connector sync status, and search anomalies through the admin API.

Webhook Registration

// Register a source system webhook that pushes to Glean Indexing API
const response = await fetch("https://yourapp.com/admin/webhooks", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    url: "https://yourapp.com/webhooks/glean-indexer",
    events: ["document.indexed", "permission.changed", "connector.synced", "search.anomaly"],
    secret: process.env.GLEAN_WEBHOOK_SECRET,
  }),
});

Signature Verification

import crypto from "crypto";
import { Request, Response, NextFunction } from "express";

function verifyGleanSignature(req: Request, res: Response, next: NextFunction) {
  const signature = req.headers["x-glean-signature"] as string;
  const expected = crypto.createHmac("sha256", process.env.GLEAN_WEBHOOK_SECRET!)
    .update(req.body).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).json({ error: "Invalid signature" });
  }
  next();
}

Event Handler

import express from "express";
const app = express();

app.post("/webhooks/glean-indexer", express.raw({ type: "application/json" }), verifyGleanSignature, (req, res) => {
  const event = JSON.parse(req.body.toString());
  res.status(200).json({ received: true });

  switch (event.type) {
    case "document.indexed":
      confirmIndexStatus(event.data.datasource, event.data.doc_id); break;
    case "permission.changed":
      reindexPermissions(event.data.datasource, event.data.object_id); break;
    case "connector.synced":
      logSyncMetrics(event.data.connector_name, event.data.docs_processed); break;
    case "search.anomaly":
      alertOps(event.data.query_pattern, event.data.anomaly_type); break;
  }
});

Event Types

EventPayload FieldsUse Case
document.indexeddatasource, doc_id, index_time_msConfirm content is searchable
permission.changeddatasource, object_id, new_aclRe-sync access controls
connector.syncedconnector_name, docs_processed, errorsMonitor connector health
search.anomalyquery_pattern, anomaly_type, severityDetect unusual search behavior
document.deleteddatasource, doc_id, deleted_byAudit content removal

Retry & Idempotency

const processed = new Set<string>();

async function handleIdempotent(event: { id: string; type: string; data: any }) {
  if (processed.has(event.id)) return;
  await routeEvent(event);
  processed.add(event.id);
  if (processed.size > 10_000) {
    const entries = Array.from(processed);
    entries.slice(0, entries.length - 10_000).forEach((id) => processed.delete(id));
  }
}

Error Handling

IssueCauseFix
Index rejectedDocument exceeds size limitChunk large documents before indexing
Permission deniedStale OAuth token for connectorRefresh connector credentials in admin
Duplicate documentsSource sends create + update rapidlyDeduplicate by doc_id before indexing
Connector timeoutSource API rate limitedImplement exponential backoff in connector

Prerequisites

  • A secret-manager webhook secret, an approved event origin allowlist, a replay-window policy, and an opaque event ledger.
  • A sandbox receiver with fictional events and a tested disable/rollback control for the consumer.
  • Data-owner approval for every event type that can alter an index, permission mapping, or retention state.

Instructions

  1. Authenticate the source before parsing, validate timestamp and event ID, and reject unknown origins, stale deliveries, and malformed payloads.
  2. Store only a bounded, redacted event envelope and use event ID plus target revision as the idempotency key.
  3. Validate source ACL and destination before enqueueing work; quarantine uncertainty rather than creating or changing indexed content.
  4. Process one canary source first, observe synthetic allow/deny outcomes and queue health, then promote or disable the consumer.
  5. Retry transient failures within a fixed budget and route exhausted events to a reviewed dead-letter path without replaying non-idempotent writes.

Output

Return an event receipt with event type, opaque event ID, signature/timestamp result, target datasource, idempotency outcome, queue state, canary result, and rollback reference. Exclude payload content and signatures.

Examples

type=document.updated; event=evt-opaque-9; signature=pass; replay=absent; source=sandbox-guides; enqueue=once; allow=pass; rollback=consumer-disabled proves the event boundary.

Resources

Next Steps

See glean-security-basics.

Signals

GitHub stars
3k
Forks
415
Last commit
Oct 2026
Advanced
Item type
skill
Key
glean-webhooks-events
Source
github.com/jeremylongshore/tons-of-skills-marketplace