DEV Community

Dropwatch Notes
Dropwatch Notes

Posted on

A valid webhook signature doesn't stop duplicate fulfillment

Suppose your payment handler verifies a request and then sends a download link. The first request works. A second copy arrives with the same valid signature. Should it send the link again?

Signature verification and duplicate handling answer different questions. The first asks whether the bytes were signed with the expected secret. The second asks whether this delivery has already caused work.

I built a small Node.js lab to make that distinction visible. It uses synthetic Whop-style events, makes no network requests, and charges nothing.

Run the failure cases locally

With Node.js 22 or newer:

git clone https://github.com/vonudimh/whop-webhook-lab.git
cd whop-webhook-lab
node demo.mjs
node --test
Enter fullscreen mode Exit fullscreen mode

There are no packages to install. The demo prints:

Attempt 1: verified payment.succeeded; dry-run only, nothing delivered
Attempt 2: duplicate skipped
Tampered payload: rejected
Enter fullscreen mode Exit fullscreen mode

The first two attempts use the same payload and headers. Both pass the signature check. The difference is that the second attempt finds the delivery identifier in a set.

The final attempt changes the payload while retaining the original signature. Verification rejects it before any delivery work runs.

Don't turn the demo set into your production design

The example deliberately uses an in-memory set so the control flow is easy to read. Restart the process and its history disappears. Run two server instances and each has its own set.

A database-backed implementation introduces another question: when do you record success?

record delivery as done
process crashes
send download link  <-- never happens
Enter fullscreen mode Exit fullscreen mode

Reversing those operations has its own failure window:

send download link
process crashes
record delivery as done  <-- never happens
retry sends another link
Enter fullscreen mode Exit fullscreen mode

One approach is to insert a uniquely identified delivery and a pending fulfillment job in the same transaction. A separate worker attempts the job and records its outcome. That closes the gap between accepting the event and scheduling work; it does not magically make the downstream action happen exactly once. If the delivery provider supports an idempotency key, use one that identifies the intended action. Otherwise decide how to reconcile uncertain outcomes.

The lab does not implement that database or worker. It lets you examine the checks before introducing them.

Keep the signed bytes intact

Another included test parses the JSON and serializes it with indentation. The resulting JSON has the same values but different bytes, so the original signature no longer matches.

The other tests cover a wrong secret, an altered delivery identifier, timestamps outside the accepted window, malformed signatures, and signed invalid JSON. All six tests passed on the publicly downloaded files during this experiment.

Scope and next test

Update, September 10: I also tested a provider-issued sample. A temporary Cloudflare receiver imported the same verifier, and Whop's test-event API sent it a signed payment.succeeded event. The response was HTTP 200 with verified: true. An unsigned request returned 401. The temporary webhook and receiver were deleted afterward. Sanitized result.

That verifies one Whop-issued sample. It does not establish production reliability, real-payment fulfillment or durable duplicate handling. Before adapting the example, test your own endpoint and its recovery from failures.

Whop's current webhook guide documents the current v1 signing scheme, timestamp window, repeated delivery identifiers and test-event workflow. Review the current secret and version conventions rather than applying this example to older webhook formats.

The source repository includes the verifier, fixtures and tests. Its setup guide contains an optional Whop partner link; this project may earn commission from eligible activity through it. Running the code requires no Whop account.

Written with AI assistance; results refer to actual local execution and the separately identified provider-issued sample. No customer payments or production fulfillment were tested.

Top comments (0)