hooksig / Square
HMAC-SHA256 · base64 · no timestamp
Square
The odd one out: Square signs the delivery URL together with the body.
Try it — live, in your browser
Request — edit anything
Verify this in code
import { verify } from "hooksig";
const result = await verify("square", {
payload: rawBody,
headers: request.headers,
secret: env.WEBHOOK_SECRET,
url: request.url, // square signs the URL
});
if (!result.verified) {
return new Response(result.reason, { status: 400 });
}no match — signature invalid
Signed string
HMAC-SHA256( secret, signed string ) = expected
Expected vs provided
expected
provided
How it's built
The scheme
- Header
x-square-hmacsha256-signature - AlgorithmHMAC-SHA256
- Encodingbase64
- Timestampnone — add your own if you need it
- SignsHMAC-SHA256 over `{notificationURL}{body}`, base64, keyed by the webhook signature key.
Gotchas
- Square signs the URL plus the body. If your public URL differs from what Square calls (proxy, trailing slash), it fails.
- The signed string is the notification URL concatenated directly with the body, no separator.
- The digest is base64.
- No timestamp is signed, so add your own replay protection if needed.