feat(helpers): verify webhook signatures from the Rust SDK - #7
Draft
antonwhop wants to merge 1 commit into
Draft
Conversation
Restores the verification half of the `client.webhooks.unwrap` the Stainless SDKs shipped. Fern generates from OpenAPI paths and `unwrap` was never a path, so every Fern SDK lost it; Ruby, Python and TypeScript have it back as hand-written helpers and Rust did not. `whop_sdk::helpers::verify_webhook(payload, headers, key)` returns the parsed body or a typed refusal. It computes the HMAC directly on the secret's literal bytes, which is what the backend signs with — the Standard Webhooks libraries the other SDKs lean on base64-decode their key instead, and Ruby has to base64-encode the whole secret to cancel that out. `.fernignore` keeps `src/helpers`, `tests/verify_webhook.rs`, and `src/lib.rs` with them: Rust compiles no file that no `mod` declares and the Rust generator has no `requirePaths` equivalent, so owning the crate root is the only way to register the module. The crypto dependencies come back from `extraDependencies` in whop-monorepo's `sdks/fern/generators.yml`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XV1533iUUxKJxfn4FXptWz
antonwhop
force-pushed
the
anton/verify-webhook-helper
branch
from
August 24, 2026 21:49
9c1fcfb to
f6769fc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why / Prompt
The Stainless SDKs shipped
client.webhooks.unwrap. Fern generates from OpenAPI paths andunwrapwas never a path, so every Fern SDK lost it — Ruby, Python and TypeScript have it back as hand-written helpers, Rust did not, and a Rust consumer receiving a Whop webhook had nothing in the SDK to verify it with.What changed?
whop_sdk::helpers::verify_webhook(payload, headers, key) -> Result<Value, WebhookVerificationError>, a standalone function so nothing generated is patched. HMACs the secret's literal bytes (ws_prefix included), which is what the backend signs with — Standard Webhooks libraries base64-decode their key instead. Constant-time viahmac'sverify_slice; ±5 min tolerance enforced before the signature so a replay is refused for its timestamp; unknown signature versions skipped, not refused. 13 tests, every fixture signed the way the backend signs, never with the code under test..fernignorekeepssrc/helpersandsrc/lib.rs— the second is load-bearing:lib.rsis generated, Rust compiles no file nomoddeclares, and the Rust generator has norequirePathsequivalent. A local generation atfern-rust-sdk:0.46.0reproduces the tree'slib.rsbyte for byte apart from the two lines added here. The tests are inline rather than undertests/: an integration test is a second binary linked against the whole crate andcargo testwas killed on the runner with it, green without it.Cargo.tomlgainsbase64,hmac,sha2; it is generated, so whop-monorepo#25253 restores them viaextraDependencies.Verified against a genuine backend delivery: all 7
webhook.*e2e scenarios go skip → pass. Nothing published, nothing tagged.🤖 Generated with Claude Code
https://claude.ai/code/session_01XV1533iUUxKJxfn4FXptWz