Verifying Webhook Signatures
SecurityDetailed security guide on validating HMAC-SHA256 signatures, preventing replay attacks, and debugging webhook payloads.
Verifying webhook signatures protects your backend against malicious actors spoofing verification events to illegitimately obtain student discounts or access privileges.
Why Signature Verification is Critical
Without signature validation, any bad actor who discovers your webhook URL could forge an HTTP POST payload claiming that a student is verified. SyncNexa signs every payload with HMAC-SHA256 using your unique webhook signing secret.
Anatomy of the Signature Header
The X-SyncNexa-Signature header contains two comma-separated key-value pairs:
sample-header.txtbash
| 1 | X-SyncNexa-Signature: t=1724155200,v1=5d41402abc4b2a76b9719d911017c5922e92c68e3768f5c9ef4780775d7b5394 |
t: Unix timestamp in seconds when the webhook was generated.v1: Hexadecimal representation of the HMAC-SHA256 signature calculated over${t}.${rawBody}using your endpoint secret.
Implementation Examples
typescript
| 1 | import crypto from 'crypto'; |
| 2 | |
| 3 | export function isSignatureValid( |
| 4 | rawBody: Buffer | string, |
| 5 | sigHeader: string, |
| 6 | secret: string |
| 7 | ): boolean { |
| 8 | const [tPart, v1Part] = sigHeader.split(','); |
| 9 | const timestamp = tPart?.split('=')[1]; |
| 10 | const signature = v1Part?.split('=')[1]; |
| 11 | |
| 12 | if (!timestamp || !signature) return false; |
| 13 | |
| 14 | const payload = `${timestamp}.${rawBody.toString('utf8')}`; |
| 15 | const expected = crypto |
| 16 | .createHmac('sha256', secret) |
| 17 | .update(payload) |
| 18 | .digest('hex'); |
| 19 | |
| 20 | return crypto.timingSafeEqual( |
| 21 | Buffer.from(expected, 'utf8'), |
| 22 | Buffer.from(signature, 'utf8') |
| 23 | ); |
| 24 | } |
Replay Attack Mitigation
Always check that the timestamp t is within a reasonable tolerance (e.g. 5 minutes or 300 seconds) of the current server time:
timestamp-guard.tstypescript
| 1 | const currentTimestamp = Math.floor(Date.now() / 1000); |
| 2 | if (Math.abs(currentTimestamp - Number(timestamp)) > 300) { |
| 3 | throw new Error('Webhook timestamp outside allowed tolerance window.'); |
| 4 | } |
Debugging Signature Failures
Raw Body vs Parsed JSON
Ensure you calculate the HMAC over the exact RAW unparsed HTTP body string or buffer. If your web framework parses JSON before computing the HMAC (e.g. `body-parser` formatting whitespace or key order), the calculated signature will not match.
Was this page helpful?