Webhooks
Lockwell POSTs a signed HMAC-SHA256 event to your endpoint on object create or remove, and verifyWebhook checks each delivery in constant time.
Lockwell can POST a signed event notification to your endpoint whenever an object is created or removed in a bucket. You configure the target with the native client, and you verify each delivery with a constant-time HMAC in one call.
Webhook is the only delivery target. SNS, SQS, and Lambda destinations are a deliberate non-goal (the server returns
501). There is one HTTP(S) target per configuration.
Keep credentials out of the target URL Query strings are accepted, but a failed delivery currently records
the full target URL in the job's LastError and append-only audit reason, and operator job output can print it. Use a
credential-free HTTPS URL with no tokens, API keys, or secrets in its query string. Authenticate deliveries by verifying
Lockwell's HMAC signature headers with the generated signing secret.
Notifications are a native-only surface. The S3 PutBucketNotificationConfiguration path enforces the same webhook-only
rule, and there is no admin-UI control for it.
Configure a notification
setBucketNotification wires a webhook for the bucket's s3:ObjectCreated:* and s3:ObjectRemoved:* events, with
optional prefix / suffix key filters. The Node helper accepts the friendly shorthand object-created /
object-removed (or the canonical s3:Object* names) and the { prefix, suffix } filter object.
const created = await native.setBucketNotification("uploads", {
id: "uploads-events",
webhookUrl: "https://my-app.example.com/hooks/lockwell",
events: ["object-created", "object-removed"],
filters: { prefix: "incoming/", suffix: ".pdf" },
});
const signingSecret = created.configs[0].signingSecret; // shown once; store securely
// Read it back. The view carries `hasSecret`; signingSecret is omitted:
const { configs } = await native.getBucketNotification("uploads");
// Clear it:
await native.deleteBucketNotification("uploads");Passing an empty config list clears the bucket's notification configuration (S3 "clear" semantics). Configuring a
notification needs the admin verb on the bucket, so a read-only or data-only key is denied with a 403.
How a delivery is signed
Each delivery carries two headers:
X-Lockwell-Signatureis the lowercase-hexHMAC-SHA256of the exact request body bytes.X-Lockwell-Eventis the event type (for quick routing; not part of the signature).
The payload body is S3-event-shaped: { "Records": [ { "eventName": ..., "s3": { ... } } ] }.
Verify an incoming delivery
Verify against the raw request bytes with verifyWebhook, which reproduces the server's signing exactly and compares in
constant time.
Verify the exact bytes Sign the raw body you received. Do not parse and re-serialize the JSON first.
Re-encoding changes the bytes and breaks the signature, so verification fails even on a genuine delivery. Read the raw body, verify, then parse.
import { verifyWebhook, WEBHOOK_SIGNATURE_HEADER_NAME } from "@kelphect/sdk";
app.post("/hooks/lockwell", async (req, res) => {
const raw = await readRawBody(req); // exact bytes; do NOT JSON.parse then re-stringify
const sig = req.headers[WEBHOOK_SIGNATURE_HEADER_NAME.toLowerCase()];
const ok = await verifyWebhook(raw, sig, process.env.WEBHOOK_SECRET);
if (!ok) return res.status(401).end();
const event = JSON.parse(raw.toString("utf8"));
for (const record of event.Records ?? []) {
/* ...do real work... */
}
res.json({ verified: true });
});verifyWebhook is exported standalone (no kit instance needed) and is edge-safe in Node. It uses WebCrypto HMAC
plus a constant-time compare with no node:crypto, so it runs unchanged on Cloudflare Workers, Vercel Edge, Bun, and
Deno. See edge runtimes.
About the signing secret
Lockwell generates a 256-bit per-config secret server-side, returns it exactly once when a new config ID is created, and
stores only encrypted material thereafter. Capture signingSecret from the creation response and put it in the
receiver's secret manager. GET and same-ID updates expose only hasSecret; losing the value requires rotation through a
new config ID. S3 PUT exposes the equivalent one-time map in X-Lockwell-Webhook-Secrets.
If your receiver has no secret to verify against, fail closed (reject the delivery) rather than trust an unverified body.
Next steps
- The app kit.
verifyWebhookis also a method onLockwellKit. - Edge runtimes. Receive and verify webhooks on a Worker.
- Data operations. Act on the object the event points to.
Versioning
Keep every write of a key with Lockwell versioning, list versions and delete markers, restore a deleted object, and handle suspended-state null versions.
When not to use Lockwell
The disqualification checklist. What Lockwell refuses to do, what it does instead, and which tool to reach for when a refusal rules it out. If a row on this page matches your workload, do not use Lockwell.