Glean Webhooks Events
Implement event-driven Glean indexing triggered by source system webhooks from GitHub, Confluence…
by jeremylongshore · MIT
The skill
Implement event-driven Glean indexing triggered by source system webhooks from GitHub, Confluence, Notion, and other content platforms. Trigger: "glean webhooks", "glean event indexing", "incremental glean index".
Glean Webhooks & Events
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
| Event | Payload Fields | Use Case |
|---|---|---|
document.indexed |
datasource, doc_id, index_time_ms |
Confirm content is searchable |
permission.changed |
datasource, object_id, new_acl |
Re-sync access controls |
connector.synced |
connector_name, docs_processed, errors |
Monitor connector health |
search.anomaly |
query_pattern, anomaly_type, severity |
Detect unusual search behavior |
document.deleted |
datasource, doc_id, deleted_by |
Audit 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
| Issue | Cause | Fix |
|---|---|---|
| Index rejected | Document exceeds size limit | Chunk large documents before indexing |
| Permission denied | Stale OAuth token for connector | Refresh connector credentials in admin |
| Duplicate documents | Source sends create + update rapidly | Deduplicate by doc_id before indexing |
| Connector timeout | Source API rate limited | Implement 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
- Authenticate the source before parsing, validate timestamp and event ID, and reject unknown origins, stale deliveries, and malformed payloads.
- Store only a bounded, redacted event envelope and use event ID plus target revision as the idempotency key.
- Validate source ACL and destination before enqueueing work; quarantine uncertainty rather than creating or changing indexed content.
- Process one canary source first, observe synthetic allow/deny outcomes and queue health, then promote or disable the consumer.
- 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.
Use this skill
Connect your agent to the Skill Store MCP server and ask for it by ref: jeremylongshore/glean-webhooks-events-13805031132.
Details
- Publisher
- jeremylongshore
- Category
- Software Development › APIs & backend
- License
- MIT
- Updated
- 2026-10-19
References
More from jeremylongshore
- Agency Os Notion-as-source-of-truth dispatch board for running your work like an AI agency.
- Audit Use when the user wants a code review on recent changes — quality, spec, security, or performance…
- Background Use when the user wants to see, inspect, cancel, or prune background agents fired during prior ch…
- Bridge Use when the user wants hyperflow's behavioral rules to apply outside the terminal CLI — in Claud…