Storeware Docs

Verifying requests

How every call from Storeware to your app is signed, and how to check it.

Every request Storeware makes to your app (webhooks, tools, knowledge and storefront blocks) is signed the same way. Verify v2, and never accept v1 alone: it signs no kind, so a webhook captured on its way to you could be replayed at your tool endpoint.

Not writing JavaScript? Verifying in other languages has the same checks in Python, PHP, Go and Ruby, and test vectors to run them against.

Verifying one

Every delivery is signed with the secret shown once when you created the endpoint. Compare it in constant time and reject anything you cannot verify: an unverified webhook is an instruction from anybody who guessed your URL.

import { verifyWebhook } from '@storeware/plugin-sdk/webhooks';

// The SDK does this for you, and is the recommended route. It throws on
// anything it cannot verify, so a forgotten check cannot pass silently.
const event = verifyWebhook({
  secret: SECRET,
  rawBody,
  signatureHeader: req.get('storeware-signature'),
});

One header, storeware-signature, carrying the time and two signatures:

storeware-signature: t=1754400131,v1=9f2ae1c4...,v2=41c07b2e...
storeware-kind: webhook

v2 is HMAC-SHA256 over `${t}.${kind}.${rawBody}` with your secret, hex encoded, where kind is webhook for a webhook. The same secret signs every call Storeware makes to an app (webhooks, tool calls, knowledge requests, storefront blocks), and the kind inside v2 is what stops a request captured on its way to one of your endpoints from verifying at another. v1 is the older `${t}.${rawBody}` signature, still sent so integrations written against it keep working; it says nothing about the kind, so check v2 in anything new.

If you would rather not take the dependency, compute v2 yourself and compare in constant time, and reject anything where t is more than five minutes old: a signature stays valid forever without that check, so a captured request can be replayed indefinitely.

Verify against the raw body, before any JSON parsing. Re-serialising an object does not reproduce the bytes that were signed, and a handler that parses first will fail every signature and eventually be "fixed" by removing the check.

On this page