Agent skill

Scalekit Webhooks Skill

Receive and verify Scalekit webhooks. Use when setting up a Scalekit webhook endpoint, getting the whsec_ signing secret, debugging webhook-id / webhook-timestamp / webhook-signature (Standard Webhooks) verification, or handling events like organization.directory.user_created, organization.created, organization.sso_enabled, user.login and role.updated.

Install this skill

npx skills add hookdeck/webhook-skills --skill scalekit-webhooks


Scalekit provides authentication for B2B SaaS (organizations, SSO, SCIM directory sync, RBAC, sessions) and AI agents (connected accounts, tool calling). Its webhooks report organization, user, directory, SSO, role/permission and connected-account events. Every delivery is signed with Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature).

When to Use This Skill

  • How do I receive Scalekit webhooks?
  • How do I verify a Scalekit webhook signature?
  • Where do I find my Scalekit webhook signing secret (whsec_...)?
  • How do I handle organization.directory.user_created or organization.sso_enabled events?
  • Why does Scalekit webhook verification fail?
  • Why do Hookdeck retries of a Scalekit webhook fail verification after a few minutes?

Verification (core)

HeaderValue
webhook-idDelivery ID, the same on every retry
webhook-timestampUnix seconds of this attempt
webhook-signatureOne or more space-separated v1,<base64 HMAC-SHA256> entries

Signed content is {webhook-id}.{webhook-timestamp}.{raw body}. The key is the base64-decoded part of the secret after whsec_. Verify with the standardwebhooks library (npm, PyPI), which compares every v1 entry in constant time:

const { Webhook } = require('standardwebhooks');

// rawBody: exact request body string, NOT re-serialised JSON
const prefix = req.get('webhook-id') ? 'webhook' : 'svix'; // fall back to svix-* like Hookdeck
const event = new Webhook(process.env.SCALEKIT_WEBHOOK_SECRET).verify(rawBody, {
  'webhook-id': req.get(`${prefix}-id`),
  'webhook-timestamp': req.get(`${prefix}-timestamp`),
  'webhook-signature': req.get(`${prefix}-signature`),
}); // returns the parsed event; throws → respond 400

Python: from standardwebhooks.webhooks import Webhook, then Webhook(secret).verify(await request.body(), {...same three keys...}). Scalekit's own SDKs offer verifyWebhookPayload(secret, headers, payload); the library above uses the same algorithm.

Timestamp window. The library always enforces a fixed 5-minute window. Hookdeck retries and replays resend the original timestamp, so the library rejects late retries after 5 minutes. A manual verifier (HMAC-SHA256 over {id}.{timestamp}.{raw body}, every v1, entry compared in constant time) where the window can be dropped or widened is in references/verification.md.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Envelope

{ "spec_version": "1", "id": "evt_100000000000000001", "object": "DirectoryUser",
  "environment_id": "env_100000000000000001", "occurred_at": "2026-10-01T09:30:00.000Z",
  "organization_id": "org_100000000000000001", "type": "organization.directory.user_created",
  "data": { "id": "diruser_100000000000000001", "email": "kai@example.com", "active": true } }

The event type is the top-level type. id (evt_…) is unique per event and differs from webhook-id; object names the data's type and is omitted on domain events; organization_id is not sent on user.* events. Dedupe on webhook-id or id, not data.id.

Common Event Types

EventFires when
organization.created / organization.updated / organization.deletedAn organization changes
organization.directory.user_created / _updated / _deletedA SCIM directory user is provisioned, changed or removed
organization.directory.group_created / _updated / _deletedA directory group changes
organization.directory_enabled / organization.directory_disabledA directory is switched on or off
organization.sso_enabled / organization.sso_disabledAn SSO connection is switched on or off
user.signup / user.login / user.logoutA user signs up, logs in or out
user.organization_membership_created / _updated / _deletedA membership changes
role.created / role.updatedA role changes

All documented types (43; the list may not be exhaustive): references/overview.md and Scalekit's webhook guide.

Delivery Semantics

  • Return a 2xx within 30 seconds (201 recommended); enqueue and process asynchronously, but verify before queueing. Missing headers or a bad signature: return 400 (or 401).
  • Any other status, including 4xx, is retried: 8 attempts (immediately, 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 10 h), about 27 h 35 min. An endpoint failing every delivery for 5 days is disabled.
  • At least once and possibly out of order: dedupe, and compare occurred_at before overwriting newer data.
  • No challenge or validation request is documented.

Environment Variables

SCALEKIT_WEBHOOK_SECRET=whsec_xxxxx   # full whsec_ value; handlers fail closed (500) without it

One signing secret per endpoint, from Dashboard > Webhooks > any endpoint.

Local Development

# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 scalekit --path /webhooks/scalekit

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 13, 2026

View on GitHub →