A 401 from your webhook route usually means the request reached the application but authentication failed. Do not bypass verification to make the test green. Use the workbench delivery timestamp and event ID, then inspect each input to the HMAC calculation without logging the secret itself.
The highest-frequency cause is body mutation: JSON middleware parses the payload before signature verification, and the application signs a newly serialized string rather than the exact bytes received. Environment mismatch, stale timestamps, stripped headers, and encoding differences are the next checks.
Design principles
The boundaries that keep this flow reliable
Reproduce one event
Use a single test event and correlate its event ID across delivery and application logs. Multiple changing inputs obscure the cause.
Log safe diagnostics
Record body byte length, timestamp, header presence, algorithm, and computed digest prefix only in protected logs. Never log the secret.
Fix the boundary
Configure raw-body handling, proxy forwarding, and secrets at deployment level instead of adding insecure fallback verification.
Implementation workflow
From request to a durable result
- 1
Confirm header arrival
Verify X-VyaparGateway-Signature and X-VyaparGateway-Timestamp reach the application with no proxy allowlist removing them.
- 2
Compare the active secret
Confirm the deployed environment uses the current dashboard webhook secret and that a recent rotation reached every instance.
- 3
Inspect raw-body timing
Ensure the exact bytes are captured before JSON parsing, decompression transformations, or framework body coercion.
- 4
Validate message construction
Use timestamp + '.' + raw body, HMAC-SHA256, expected hexadecimal encoding, constant-time comparison, and an accurate server clock.
Safe diagnostic checklist in code
tslogger.info("webhook verification input", {
eventTrace: request.headers.get("X-Request-Id"),
signaturePresent: Boolean(signature),
timestampPresent: Boolean(timestamp),
rawBodyBytes: rawBody.byteLength,
clockSkewSeconds: Math.round(Date.now() / 1000) - Number(timestamp),
// Never log webhookSecret or the complete payment payload.
}); Production checklist
Verify before going live
- Signature and timestamp headers reach the application
- All deployed instances use the same active environment secret
- Raw bytes are captured before JSON parsing
- Server clock is synchronized
- Digest encoding and timing-safe comparison match the documented recipe
Failure recovery
401 cause map
Works locally, fails behind proxy
Check whether the proxy forwards custom headers and whether it decompresses or rewrites the request body before your application sees it.
Only some requests fail
One deployment instance may have an old secret, or a subset of payloads may expose a body parser difference. Compare instance ID and byte length.
Fails after secret rotation
Update every instance atomically or support a short, controlled dual-secret rotation window. Remove the previous secret promptly after confirmation.
FAQ
Questions developers ask
Should I return 200 when signature verification fails?
No. Reject unauthenticated requests. Fix the verification inputs rather than hiding the failure.
Can whitespace change the signature?
Yes. HMAC covers exact bytes, so whitespace, newline, escaping, or key-order changes produce a different digest.
What if my framework cannot provide raw body?
Configure route-specific raw-body middleware or use a lower-level request interface before the framework parses JSON.