Agent skill
Glean Webhooks Skill
Receive and verify Glean trigger webhooks (Standard Webhooks signatures). Use when setting up a Glean Triggers webhook receiver, debugging webhook-id / webhook-timestamp / webhook-signature verification, handling the signing_secret returned by POST /api/triggers, or parsing trigger deliveries such as CONTENT_SCHEDULE meeting events.
Install this skill
npx skills add hookdeck/webhook-skills --skill glean-webhooks
Glean Triggers subscribe your application to content events (a review requested from you, a new call you took part in, the half hour before a meeting starts) and deliver each one to your HTTPS endpoint as a signed webhook that follows the Standard Webhooks specification.
When to Use This Skill
- How do I receive Glean trigger webhooks?
- How do I verify
webhook-id/webhook-timestamp/webhook-signaturefrom Glean? - Where do I get the Glean signing secret? (Only returned once, by
POST /api/triggers.) - Why is my Glean webhook signature verification failing?
- Why are retried or replayed Glean deliveries rejected after 5 minutes?
- How do I handle a
CONTENT_SCHEDULEtrigger delivery?
Verification (core)
Each delivery carries three headers:
webhook-id: msg_... # stable across retries: use as idempotency key
webhook-timestamp: 1614265330 # Unix seconds
webhook-signature: v1,g0hM9SsE... # space-delimited "v1,<base64>" entries
The signed string is {webhook-id}.{webhook-timestamp}.{raw body}, HMAC-SHA256, keyed with the base64-decoded signing secret after removing the whsec_ prefix, base64-encoded. Use the official standardwebhooks library (the svix library works too) and pass the raw body:
const { Webhook } = require('standardwebhooks');
const wh = new Webhook(process.env.GLEAN_SIGNING_SECRET); // "whsec_..." — library decodes it
wh.verify(rawBody, { // rawBody: raw Buffer/string, never re-serialized JSON
'webhook-id': req.headers['webhook-id'],
'webhook-timestamp': req.headers['webhook-timestamp'],
'webhook-signature': req.headers['webhook-signature'],
}); // throws on a bad signature or a timestamp >5 min off
const event = JSON.parse(rawBody.toString('utf8'));
from standardwebhooks.webhooks import Webhook, WebhookVerificationError
wh = Webhook(os.environ["GLEAN_SIGNING_SECRET"])
wh.verify(raw_body, {"webhook-id": ..., "webhook-timestamp": ..., "webhook-signature": ...})
If you set delivery.auth with type: BEARER when creating the trigger, Glean also sends Authorization: Bearer <secret>. Treat it as an extra check on top of the signature, never a replacement.
Retries and the 5-minute window
The standardwebhooks and svix libraries always enforce a fixed 5-minute timestamp window. Retries and replays through Hookdeck (and any other relay) resend the original webhook-timestamp, so the library rejects any delivery that reaches your handler more than 5 minutes after Glean signed it. Glean treats a 4xx (other than 408/429) as terminal, so a rejected late delivery is dropped. If you receive through Hookdeck or need replays to work, use the manual verifier in references/verification.md: it recomputes the HMAC over {id}.{timestamp}.{raw body} and lets you drop or widen the window, comparing every space-separated v1,<base64> entry in constant time.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Trigger Deliveries
Triggers are created from per-datasource presets. Preset ids are per deployment (discover them with GET /api/trigger-presets), so there is no fixed event list. Glean's public docs don't publish the delivery body; the fields below come from the test fixture in Glean's customer-meeting-prep cookbook recipe, so treat them as illustrative and confirm them against a real delivery:
| Field | Example | Meaning |
|---|---|---|
event_type | CONTENT_SCHEDULE | What kind of trigger fired (CONTENT_SCHEDULE for "a meeting starts in 30 minutes") |
trigger_id / trigger_name | spec-7d2f... | Which trigger produced it |
datasource / doc_type | GOOGLECALENDAR / event | Source content |
doc_id, title, view_url | The matched document | |
event_time, time_offset_seconds | 2026-08-25T18:30:00Z / 1800 | Schedule triggers only |
Always keep a default branch for event_type values you do not handle.
Environment Variables
# Returned only once by POST /api/triggers (cannot be read back); starts with whsec_
GLEAN_SIGNING_SECRET=whsec_xxxxx
# Optional: the delivery.auth BEARER secret, if you configured one
GLEAN_DELIVERY_BEARER=
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 glean --path /webhooks/glean
Reference Materials
- references/overview.md - Glean Triggers concepts, payload, retries
- references/setup.md - Creating a trigger and capturing the signing secret
- references/verification.md - Library and manual verification, replay handling