Small Express server that verifies Quicknode Streams webhook HMAC-SHA256 signatures and supports gzip-compressed bodies (Content-Encoding: gzip), matching How to Validate Incoming Streams Webhook Messages.
Signature input is: nonce + timestamp + payload (UTF-8 string), keyed by your Stream security token.
Quicknode Streams signs the uncompressed JSON body (the same bytes that are gzipped for transport when compression is enabled). Your verifier must run HMAC on that decoded UTF-8 string, not on the raw gzip octets.
This app uses express.raw(), which is implemented with body-parser. When the request has Content-Encoding: gzip, body-parser decompresses the body by default before the bytes are stored in req.body. So req.body is already the JSON bytes; the code uses req.body.toString("utf8") as the payload for verification.
Important details:
- The
Content-Encoding: gzipheader is not stripped. It can still be present even thoughreq.bodyholds decoded JSON—do not callzlib.gunziponreq.bodyin that case or you will get errors such as zlib’s “incorrect header check.”
-
Copy
.env.exampleto.envand setQN_STREAM_SECRETto the security token from your Stream’s Settings in the Quicknode dashboard. -
Install and run (default port 9999):
npm install
npm startOptional: PORT=3000 npm start
- Expose with ngrok and set the Stream webhook URL to
https://<your-tunnel>/webhook.
ngrok http 9999- Do not commit
.envor embed the token in source code for real deployments. - Consider timestamp skew checks to reduce replay risk (see the guide’s best practices).