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-signature from 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_SCHEDULE trigger 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:

FieldExampleMeaning
event_typeCONTENT_SCHEDULEWhat kind of trigger fired (CONTENT_SCHEDULE for "a meeting starts in 30 minutes")
trigger_id / trigger_namespec-7d2f...Which trigger produced it
datasource / doc_typeGOOGLECALENDAR / eventSource content
doc_id, title, view_urlThe matched document
event_time, time_offset_seconds2026-08-25T18:30:00Z / 1800Schedule 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →