Skip to main content

stytch-webhooks

Receive and verify Stytch webhooks (delivered by Svix). Use when setting up Stytch webhook handlers, debugging svix-signature verification, or handling auth events like direct.user.create, direct.member.update, or direct.organization.create.

Quellinformationen

Repository
hookdeck/webhook-skills
Letzte Quellaktivität
21. Oktober 2026 um 13:56
Erkannte Sprache von SKILL.md
Englisch
Sterne
94
Forks
16

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
21 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
stytch-webhooks
description
Receive and verify Stytch webhooks (delivered by Svix). Use when setting up Stytch webhook handlers, debugging svix-signature verification, or handling auth events like direct.user.create, direct.member.update, or direct.organization.create.
license
MIT
metadata
{"author":"hookdeck","version":"0.1.0","repository":"https://github.com/hookdeck/webhook-skills"}
# Stytch Webhooks ## When to Use This Skill - How do I receive Stytch webhooks? - How do I verify Stytch webhook signatures (`svix-signature`)? - How do I handle `direct.user.create` or `direct.member.update` events? - Why is my Stytch webhook signature verification failing? - How do I keep my database in sync with Stytch users, members, and organizations? ## Resources - `references/overview.md` - What Stytch webhooks are, event type structure, payload fields - `references/setup.md` - Create an endpoint in the Stytch Dashboard and get the signing secret - `references/verification.md` - Svix signature verification details and gotchas - `examples/` - Runnable Express, Next.js, and FastAPI examples ## Verification (core) Stytch webhooks are delivered by [Svix](https://docs.svix.com/receiving/verifying-payloads/how-manual). Each request carries `svix-id`, `svix-timestamp`, and `svix-signature` (a space-delimited list of `v1,<base64>` entries). The signed content is `{svix-id}.{svix-timestamp}.{raw body}`, HMAC-SHA256, keyed with the **base64-decoded** part of the `whsec_` signing secret. ```javascript import crypto from 'node:crypto'; // rawBody: the exact bytes received (express.raw() / request.text()), NOT re-serialised JSON. // toleranceSec = 0 (default) skips the timestamp window, because Hookdeck retries and replays // keep the original svix-timestamp. A positive value opts in. function verifyStytchWebhook(rawBody, headers, secret, toleranceSec = 0) { const id = headers['svix-id']; const ts = headers['svix-timestamp']; const sigs = headers['svix-signature']; if (!id || !/^\d+$/.test(ts ?? '') || !sigs) throw new Error('Missing or malformed svix-* headers'); if (toleranceSec > 0 && Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) { throw new Error('Timestamp outside tolerance'); } const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64'); // decode; don't use the string as-is const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.`).update(rawBody).digest(); const ok = sigs.split(' ').some((entry) => { const [version, sig] = entry.split(','); const given = Buffer.from(sig ?? '', 'base64'); return version === 'v1' && given.length === expected.length && crypto.timingSafeEqual(given, expected); }); if (!ok) throw new Error('Invalid signature'); return JSON.parse(rawBody.toString('utf8')); } ``` **Why not the `svix` SDK?** `new Webhook(secret).verify(...)` (npm) and `Webhook(secret).verify(...)` (PyPI) check the same signature, but they always enforce a 5-minute timestamp window with no way to turn it off. Hookdeck retries and replays carry the original `svix-timestamp`, so the SDK rejects them. The examples verify manually and make the window opt-in with `STYTCH_TIMESTAMP_TOLERANCE_SECONDS` (default `0`; a positive number of seconds turns it on; anything that isn't a whole number is treated as `0`, with a warning at startup). If you never retry or replay late, the SDK is fine. Python equivalent: ```python import base64, hashlib, hmac, time def verify_svix(raw_body: bytes, headers, secret: str, tolerance: int = 0) -> None: # tolerance = 0 (default) skips the timestamp window; a positive value opts in msg_id, ts, sigs = headers.get("svix-id"), headers.get("svix-timestamp"), headers.get("svix-signature") if not (msg_id and ts and ts.isdigit() and sigs): raise ValueError("Missing or malformed svix-* headers") if tolerance > 0 and abs(time.time() - int(ts)) > tolerance: raise ValueError("Timestamp outside tolerance") key = base64.b64decode(secret.removeprefix("whsec_")) expected = hmac.new(key, f"{msg_id}.{ts}.".encode() + raw_body, hashlib.sha256).digest() for entry in sigs.split(" "): version, _, sig = entry.partition(",") if version == "v1" and hmac.compare_digest(base64.b64decode(sig or ""), expected): return raise ValueError("Invalid signature") ``` > **For complete handlers with tests**, see [examples/express/](examples/express/), [examples/nextjs/](examples/nextjs/), [examples/fastapi/](examples/fastapi/). ## Event Types Stytch event types are `source.object_type.action`. The body carries `source` (`DIRECT`, `DASHBOARD`, `SCIM`), `object_type` (e.g. `member`), and `action` (`CREATE`, `UPDATE`, `DELETE`), but no combined `type` field, so build it: `` `${source}.${object_type}.${action}`.toLowerCase() ``. | Event | Triggered When | |-------|----------------| | `direct.user.create` | A user is created via API/SDK (Consumer projects) | | `direct.user.update` | A user is updated via API/SDK (Consumer projects) | | `direct.organization.create` | An Organization is created via API/SDK (B2B projects) | | `direct.member.update` | A Member is updated via API/SDK (B2B projects) | | `direct.saml_connection.delete` | A SAML connection is deleted via API/SDK (B2B projects) | | `dashboard.organization.create` | An Organization is created in the Stytch Dashboard | | `scim.member.update` | A Member is updated through SCIM | CREATE and UPDATE events include the full entity under a key named after `object_type` (e.g. `member`); DELETE events carry only `id`. For the complete catalog, see the Webhooks page of the Stytch Dashboard and [Stytch's webhook docs](https://stytch.com/docs/workspace-management/webhooks). ## Environment Variables ```bash STYTCH_WEBHOOK_SECRET=whsec_xxxxx # Dashboard > Webhooks > your endpoint > Signing secret STYTCH_TIMESTAMP_TOLERANCE_SECONDS=0 # Optional replay window; a positive value opts in ``` ## Local Development ```bash npx hookdeck-cli listen 3000 stytch --path /webhooks/stytch ``` No account required. Use the tunnel URL as the endpoint URL in the Stytch Dashboard. ## Gotchas - Use the **raw body** for verification; parsing JSON first changes the bytes and breaks the signature. - Webhooks have no ordering guarantee. Treat them as a signal and re-fetch the entity from the Stytch API before overwriting your data. - `svix-id` is the same on every retry of a message; use it to deduplicate. - Svix disables an endpoint after 5 days of continuous failures; re-enable it in the Dashboard. ## Recommended: webhook-handler-patterns We recommend installing the [webhook-handler-patterns](https://github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns) skill alongside this one for handler sequence, idempotency, error handling, and retry logic. Key references (open on GitHub): - [Handler sequence](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md) — Verify first, parse second, handle idempotently third - [Idempotency](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md) — Prevent duplicate processing - [Error handling](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md) — Return codes, logging, dead letter queues - [Retry logic](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md) — Provider retry schedules, backoff patterns ## Related Skills - [clerk-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/clerk-webhooks) - Clerk auth webhooks (Svix-style signatures) - [svix-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/svix-webhooks) - Generic Svix signature verification - [vanta-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/vanta-webhooks) - Vanta compliance webhooks via Svix - [sendoso-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/sendoso-webhooks) - Sendoso webhooks via Svix - [resend-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/resend-webhooks) - Resend email webhooks via Svix - [stitch-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/stitch-webhooks) - Stitch webhooks via Svix - [stripe-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks) - Stripe payment webhook handling - [webhook-handler-patterns](https://github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns) - Handler sequence, idempotency, error handling, retry logic - [hookdeck-event-gateway](https://github.com/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway) - Webhook infrastructure that replaces your queue — guaranteed delivery, automatic retries, replay, rate limiting, and observability for your webhook handlers
Auf GitHub ansehen