Sociologix
← Latest in AI

Automation · · 6 min read

Svix: verify incoming events before an AI workflow starts

Test webhook signatures against raw payloads, reject modified events and distinguish timestamp checks from duplicate-event handling.

By Sociologix Editorial

Official Svix publisher graphic with its logo, webhook illustration and product introduction.
Official Svix website sharing artwork, retrieved October 18, 2026. Publisher marketing text is shown as supplied; this tutorial uses only the local verification library.Image source ↗

Check the event before giving an agent a task

A webhook can tell an AI worker that a lead arrived or a document is ready. Your receiver should establish that the event was signed with the expected endpoint secret before it reads business instructions from the payload. Otherwise, a reachable endpoint can become an unintended way to start work.

This AI-assisted editorial guide uses the Svix JavaScript library to test that boundary locally. It signs a synthetic event with a newly generated disposable secret, verifies it and exercises seven additional scenarios. No provider account, customer data, public endpoint or AI model is involved. The observed results cover local verification behavior, rather than delivery from a live service.

1. Install the pinned SDK in a scratch directory

The checked package version is svix 2.8.0. Its official manifest requires Node.js 22 or newer and uses ES modules. The example below was executed with Node.js 24.19.0 on October 18, 2026. Use a separate folder so this experiment does not alter an existing application’s dependencies. [1]

Run the first three commands, save both JavaScript blocks below in order as verify-demo.mjs, then run the final command. Installation requires internet access. Once installed, this example makes no network requests. Its randomly generated secret exists only for these fixtures and should never become a production endpoint secret.

mkdir svix-webhook-demo
cd svix-webhook-demo
npm install --ignore-scripts "svix@2.8.0"
# Save both JavaScript blocks below as verify-demo.mjs first.
node verify-demo.mjs

2. Preserve the signed body exactly

Svix’s verification guide requires the raw request body. Parsing JSON and serializing it again can change the signed bytes. Its three Svix-branded headers carry the message ID, attempt timestamp and signature. The fixture deliberately contains whitespace so a parse-and-stringify round trip changes its representation. [2]

The signing helper below is a test fixture, not a receiver implementation. In production, the provider supplies the headers and payload; your server verifies them using the secret configured for that endpoint. Do not accept a signing secret from the incoming event or disclose the configured value in logs.

import assert from "node:assert/strict";
import { randomBytes } from "node:crypto";
import { Webhook } from "svix";

// Disposable synthetic signing secret; never log or reuse it.
const secret = "whsec_" + randomBytes(32).toString("base64");
const webhook = new Webhook(secret);
const rawBody = '{ "type": "lead.created", "lead_id": "demo_lead_1" }';
const now = new Date();
function signedHeaders(date = now) {
  const id = "msg_demo_1";
  return {
    "svix-id": id,
    "svix-timestamp": String(Math.floor(date.getTime() / 1000)),
    "svix-signature": webhook.sign(id, date, rawBody),
  };
}
const headers = signedHeaders();

3. Verify first, then parse and validate

For the pinned SDK, verify throws on verification failure and returns undefined on success. It does not return the parsed event. That behavior is explicit in the current implementation, which disables JSON parsing in the underlying verifier. Call JSON.parse after successful verification, then apply your application’s event schema. [3]

Append this block to the same file. Each rejected case uses a signed fixture or a deliberate modification; the timestamp cases are correctly signed but ten minutes outside the current time in either direction. The final assertion repeats the valid event to expose the limit of signature checking.

// Verification must succeed before parsing or using event fields.
assert.equal(webhook.verify(rawBody, headers), undefined);
const event = JSON.parse(rawBody);
assert.equal(event.type, "lead.created");
console.log("valid body: verified, then parsed");

function rejected(label, body, suppliedHeaders, verifier = webhook) {
  assert.throws(() => verifier.verify(body, suppliedHeaders));
  console.log(label + ": rejected");
}
rejected("changed value", rawBody.replace("demo_lead_1", "demo_lead_2"), headers);
rejected("reserialized JSON", JSON.stringify(JSON.parse(rawBody)), headers);
rejected("wrong secret", rawBody, headers,
  new Webhook("whsec_" + randomBytes(32).toString("base64")));
rejected("old timestamp", rawBody,
  signedHeaders(new Date(now.getTime() - 10 * 60 * 1000)));
rejected("future timestamp", rawBody,
  signedHeaders(new Date(now.getTime() + 10 * 60 * 1000)));
rejected("missing signature", rawBody, {
  "svix-id": headers["svix-id"],
  "svix-timestamp": headers["svix-timestamp"],
});

// A recent duplicate can still verify: deduplication is a separate step.
assert.doesNotThrow(() => webhook.verify(rawBody, headers));
console.log("recent duplicate: verifies again; application must deduplicate");

4. Interpret what the local run established

The original body verified and parsed. A changed lead ID, reserialized JSON, wrong secret, old timestamp, future timestamp and missing signature were all rejected. The recent duplicate verified again. All eight scenarios completed with their expected assertions in the local run.

Svix documents a five-minute timestamp window in either direction and notes that the receiving server’s clock must be accurate. This check limits how old a captured attempt may be; it does not prevent a duplicate inside the allowed window. If valid events fail in a real receiver, check clock accuracy, raw-body handling and the endpoint-specific secret before changing the policy. [4]

5. Add durable event handling after verification

The message ID stays the same when a Svix message is retried. Use that stable identifier in your own durable event record, scoped to the relevant integration or customer. A database uniqueness constraint can help coordinate competing deliveries; a process-local Set disappears on restart and cannot coordinate multiple instances. [5]

Design that record together with the work queue. Recording an event as processed before the job is safely stored can lose work after a crash. Starting the job before recording it can permit repeated execution. A transactional queue handoff or another durable coordination design needs its own failure tests; this small example does not implement one.

Finally, validate allowed event types, the referenced customer and the action the worker may perform. A valid signature does not prove that a payload field authorizes every business operation. Keep a human approval step for actions your business requires someone to review, and use explicit failure responses instead of starting the AI worker after a verification exception.

Sources & further reading

  1. Svix JavaScript package manifest: version, runtime and module format
  2. Svix documentation: verification with official libraries
  3. Svix JavaScript verification implementation
  4. Svix documentation: why verify webhooks
  5. Svix documentation: signed components and message identifiers

Build a dependable event-to-agent handoff

Sociologix can help connect incoming business events to AI workers with signature verification, data validation, duplicate handling and approval steps. Bring one workflow and the systems that send its events.

Talk to Sociologix