Verify incoming webhooks
View .mdVerify the exact request bytes before parsing JSON or processing an event. The Node.js server helper is available from @kismet-tech/sdk/server in source builds containing this change. It is not included in the published 0.1.0-beta.2 package. See the source-pinned evaluation workflow.
import { verifyKismetWebhook, KismetWebhookVerificationError,} from '@kismet-tech/sdk/server';
// Inside your Node.js Fetch-compatible request handler:const rawBody = new Uint8Array(await request.arrayBuffer());let verified;try { verified = verifyKismetWebhook({ rawBody, headers: request.headers, secret: signingSecretFromServerConfig, });} catch (error) { if (error instanceof KismetWebhookVerificationError) { return new Response('Invalid webhook', { status: 400 }); } throw error;}
// Only now parse and validate the event using its agreed version/schema.const payload: unknown = JSON.parse(new TextDecoder().decode(rawBody));// Validate the event's environment and authorized application/collection scope.// Atomically record verified.id with your durable enqueue or business write.// Acknowledge with 2xx only after that operation commits.request is your incoming Request; signingSecretFromServerConfig is the destination’s secret held on the server. Keep this code in a Node.js route, not a browser component or Edge runtime. Express users should supply the Buffer from raw-body middleware, before any JSON middleware. A parsed body re-serialized with JSON.stringify is not the original body.
Other server SDK operations, including guest authentication, can run in supported Edge runtimes. The @kismet-tech/sdk/server export selects a Web API-only entry for Next.js Edge and Workers; the synchronous webhook verifier is available only in the Node.js entry. In Next.js, use export const runtime = 'nodejs' for your webhook receiver.
Inputs and result
Section titled “Inputs and result”| Field | Meaning |
|---|---|
rawBody |
Exact bytes as Uint8Array/Node Buffer, or an exact UTF-8 string. Whitespace, key order, Unicode and trailing newlines matter. |
headers |
Fetch Headers or Node-style case-insensitive header record. Required: webhook-id, webhook-timestamp, webhook-signature. Duplicate IDs or timestamps are rejected. |
secret |
Current signing secret, or an array of up to eight active/previous secrets during a bounded rotation window. |
toleranceSeconds |
Nonnegative integer clock tolerance, default 300 seconds. Both excessively old and future timestamps fail. Zero requires the current second. |
nowSeconds |
Optional nonnegative integer Unix seconds for deterministic tests. Production should use the local clock default. |
| Result | { id, timestamp }, the authenticated header ID and delivery timestamp. No payload parsing, event-type assertion or authorization is implied. |
All verification failures use KismetWebhookVerificationError with a fixed message. The helper does not log secrets, signatures, headers or bodies. Enforce your request-body size limit before buffering the body. Protect any signing secrets in server configuration; do not include them in URLs, telemetry, error responses or client bundles.
Existing v1 wire convention
Section titled “Existing v1 wire convention”Kismet uses the Standard Webhooks header names and signs the bytes of:
<webhook-id>.<webhook-timestamp>.<exact raw body>The signature is SHA-256 HMAC encoded as padded base64, with header value v1,<signature>. For Kismet’s existing v1 signer, remove a single whsec_ prefix and use the remaining UTF-8 text as the HMAC key. Do not base64-decode or hex-decode that suffix. This key convention differs from libraries that assume every whsec_ suffix is base64-encoded key material.
The helper accepts multiple space-separated v1 signatures, repeated/coalesced signature headers, and multiple configured secrets. It checks recognized signatures with constant-time comparison and accepts a match against any configured key. Unknown signature versions are ignored, but cannot authenticate a request by themselves. It does not fall back to the legacy X-Kismet-Signature mirror or accept unsigned deliveries.
Receiver support for rotation does not itself configure sender rotation. Keep a previous secret only for the agreed overlap window and remove it afterward.
Freshness, retries and deduplication
Section titled “Freshness, retries and deduplication”The signed header timestamp must be the delivery-attempt time, including delayed retries and manual replay. The event’s occurrence timestamp can remain unchanged. A sender that reuses the original occurrence time for delayed delivery will fail the default freshness check. Confirm per-attempt timestamps before enabling a receiver with this helper; do not solve an incompatible sender by disabling replay protection.
A valid signature inside the freshness window can be replayed. Persist the verified event ID atomically with your work, scoped to the endpoint/installation. Retry of the same event must not cause a second side effect. Do not assume delivery order. A signature establishes authenticity and freshness only: separately validate the event version, type, authorized scope and TEST/LIVE environment. This helper does not introduce event types, payment authority, subscription APIs or delivery guarantees.