[webhooks] Fix signature verification bugs#12
Conversation
Fixes several issues in the webhooks overview doc: - Fixed broken URL syntax in the setup section - Added replay attack protection (reject requests older than 5 minutes) - Fixed a crash when signature header is malformed - Fixed a crash in timingSafeEqual when buffer lengths don't match - Reformatted setup steps using the Steps component
There was a problem hiding this comment.
Code Review
This pull request improves the webhook verification example in the documentation by adding timestamp validation to prevent replay attacks and ensuring safe buffer length comparisons before performing timing-safe signature checks. Feedback was provided regarding the fragility of the strict signature parsing logic, which is order-dependent and not forward-compatible, along with a suggestion for a more robust parsing implementation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const parts = signature.split(','); | ||
| if (parts.length !== 2) return false; | ||
| const [tPart, v1Part] = parts; | ||
| const timestamp = tPart.split('=')[1]; | ||
| const receivedSig = v1Part.split('=')[1]; | ||
| if (!timestamp || !receivedSig) return false; |
There was a problem hiding this comment.
The current implementation is fragile and not forward-compatible:\n\n1. Strict length check (parts.length !== 2): If Top.gg adds new fields to the signature header in the future (e.g., for versioning, metadata, or additional signatures), this check will fail and break the webhook integration for users copying this code.\n2. Order dependency: It assumes t is always the first part and v1 is the second part. If the order is reversed or changed, parsing will fail.\n\nParsing the header by splitting and matching keys in a loop is much more robust, order-independent, and forward-compatible.
const parts = signature.split(',');\n let timestamp: string | undefined;\n let receivedSig: string | undefined;\n\n for (const part of parts) {\n const [key, ...valueParts] = part.split('=');\n const value = valueParts.join('=');\n if (key === 't') timestamp = value;\n if (key === 'v1') receivedSig = value;\n }\n\n if (!timestamp || !receivedSig) return false;
Fixes several issues in the webhooks overview doc: