Parse the JSON body, then re-serialize it with these exact rules — this is what PHP's json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) produces:
| Rule | Detail |
|---|---|
| Indentation | 4 spaces per level, \n line endings |
| Key separator | ": " (colon + single space) |
| Key order | Exactly as received — never sorted |
| Forward slashes | Not escaped — https://example.com/x |
| Non-ASCII | Escaped as lowercase \uXXXX (per UTF-16 code unit) |
| Whole numbers | No fractional part — 100.0 is rendered as 100 |
| Empty object | Rendered as [], not {} |
Then HMAC-SHA256 that string with your sign key and compare it to the header, using a constant-time comparison.