Agent skill
Airweave Webhooks Skill
Receive and verify Airweave webhooks (open-source context-retrieval layer, delivered via Svix). Use when setting up Airweave webhook handlers, debugging svix-signature verification, or handling events like sync.completed, sync.failed, source_connection.auth_completed, and collection.deleted.
Install this skill
npx skills add hookdeck/webhook-skills --skill airweave-webhooks
Airweave is an open-source context-retrieval layer that syncs apps into searchable collections for agents. Its webhooks (beta) report sync-job, source-connection and collection lifecycle events. They are delivered through Svix, which implements Standard Webhooks. Every delivery is an HTTP POST with a JSON body and three signature headers: svix-id, svix-timestamp, and svix-signature. The body is flat: the event name is the top-level event_type, followed by the ids for that domain and a timestamp.
When to Use This Skill
- How do I receive Airweave webhooks?
- How do I verify Airweave webhook signatures (
svix-signature)? - How do I handle
sync.completed,sync.failedorsync.cancelledevents? - How do I react to
source_orconnection.auth_ completed collection.deleted? - Why is my Airweave webhook signature verification failing, or rejecting Hookdeck retries?
Verification (core)
Signed content is {svix-id}.{svix-timestamp}.{raw body}. Svix signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-separated v1,<signature> entries. Use the official svix library, which compares in constant time. Read the svix-* headers, and when svix-id is absent fall back to the webhook-* headers (webhook-id, webhook-timestamp, webhook-signature), as Hookdeck does.
import { Webhook } from 'svix'; // svix 2.x is ESM-only
const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.AIRWEAVE_WEBHOOK_SECRET).verify(rawBody, {
'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes
from svix.webhooks import Webhook, WebhookVerificationError
wh = Webhook(os.environ["AIRWEAVE_WEBHOOK_SECRET"]) # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
"webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
The signing secret is per subscription and starts with whsec_. Fetch it with GET /webhooks/subscriptions/{id}?include_: the response includes a secret field in the format whsec_.... Keep the full value in AIRWEAVE_WEBHOOK_SECRET. Handlers fail closed: with the variable unset they return 500 instead of accepting anything.
If you set a custom secret, paste the whsec_ secret Airweave returns for the subscription, not the value you sent.
That is Hookdeck's own configuration alert for this source. A custom secret (minimum 24 characters) can be supplied when you create the subscription, but what a verifier needs is the returned whsec_... value.
Airweave's own manual-verification snippets split svix-signature on , and take the last space-separated token, which only works for a single signature. The svix library and the manual verifier in references/verification.md handle several space-separated v1,<base64> entries correctly.
Hookdeck retries and replays hit the 5-minute window
The svix library always enforces a fixed 5-minute timestamp window, and it can't be configured. Hookdeck retries and replays resend the original svix-timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. The body has no event id, so deduplicate on the svix-id header instead of relying on the timestamp.
Common Event Types
11 events are documented (the list may not be exhaustive). The name is event_type in the JSON body. The examples handle all of these:
| Event | Triggered When |
|---|---|
sync.pending | A sync job is created and queued |
sync.running | The sync job starts processing |
sync.completed | The sync job finishes successfully (adds entities_inserted, entities_updated, entities_deleted, entities_skipped, chunks_written) |
sync.failed | The sync job hit an error (error has the details) |
sync.cancelled | The sync job was cancelled manually |
source_ | A source connection record is created (check is_authenticated) |
source_ | The OAuth flow completed and the connection is ready |
source_ | A source connection and its data were removed |
collection.created | A collection was created |
collection.updated | Collection properties changed |
collection.deleted | A collection and its data were removed |
See references/overview.md for payload fields, retries and delivery rules.
Environment Variables
# Signing secret of the webhook subscription (Svix format, starts with whsec_)
AIRWEAVE_WEBHOOK_SECRET=whsec_xxxxx
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 airweave --path /webhooks/airweave
Create a subscription with POST https://api.airweave.ai/webhooks/subscriptions (Bearer token, admin or owner role) using the tunnel URL and the event types you want, then fetch the secret with ?include_secret=true. See references/setup.md.
Reference Materials
- references/overview.md - Airweave webhook concepts, events, payloads, delivery rules
- references/setup.md - Create a subscription, get the secret, manage and recover
- references/verification.md - Svix verification details, header fallback, manual verifier, timestamp window