# Whop webhook lab: catch duplicate and tampered payment events A small, dependency-free Node.js exercise for developers adding Whop payments to an app or an automated delivery workflow. Run a signed synthetic payment event twice, then alter its body and watch verification reject it. No account, API key, network request, payment or installation is needed for the demo. Requires Node.js 22 or newer. ```sh git clone https://github.com/vonudimh/whop-webhook-lab.git cd whop-webhook-lab node demo.mjs node --test ``` Prefer a ZIP? Use GitHub's Code > Download ZIP. [Watch the 82-second walkthrough](https://www.youtube.com/watch?v=csj0tUeYuwk). Expected demo output: ```text Attempt 1: verified payment.succeeded; dry-run only, nothing delivered Attempt 2: duplicate skipped Tampered payload: rejected ``` ## The integration mistake this demonstrates One purchase can produce repeated webhook deliveries. A handler that sends a download or creates a job on every request can do the same work twice. The demo's set makes that visible; it is deliberately not a production queue. `verify.mjs` checks the raw bytes, signature and timestamp before parsing JSON. It accepts current v1 events using a `ws_` signing secret. Reformatting the JSON changes the signature. Older webhook formats are outside this example's scope. ## Connecting your own account If you decide Whop fits your business, [create a Whop account](https://whop.com/start/?a=flakyelation6d). This is our partner link; we may earn commission from eligible activity. The code also works without using that link. Use [Whop's webhook setup guide](https://docs.whop.com/developer/guides/webhooks) to create an endpoint and send its test event. Use the issued signing secret on your server, never the synthetic key in `demo.mjs`. Keep it out of browser code and Git. Before delivering anything, persist the delivery identifier and a pending job in one database transaction with a unique constraint. A worker should perform retryable fulfillment. An in-memory set loses its history on restart and doesn't coordinate multiple processes. Test failure and recovery at both the database and delivery steps. On September 10, 2026, Whop's test-event API sent a signed `payment.succeeded` sample to a temporary Cloudflare receiver importing this verifier. The receiver returned HTTP 200 with `verified: true`; an unsigned request returned 401. The temporary webhook and receiver were then deleted. This verifies one provider-issued sample, not a real purchase, production reliability or fulfillment. Test your own endpoint before production. [Sanitized test result](https://dropwatch-notes.tkmm24502-982.workers.dev/webhook-lab/provider-test.json). ## Sources and maintenance Reviewed September 10, 2026: - [Whop webhook documentation](https://docs.whop.com/developer/guides/webhooks): signing construction, five-minute timestamp window, repeated delivery identifiers and test delivery workflow. - [Whop API versioning](https://docs.whop.com/developer/api/versioning): pin the event version when integrating. The webhook documentation currently warns that the new standalone SDK helpers are pending release. This exercise uses Node's built-in cryptography instead of assuming those helpers are installed. Check the current docs before integrating. Independent example from Dropwatch Notes, not an official Whop SDK. Issues with reproducible, redacted examples are welcome; never include real signing secrets or customer payloads.