DEV Community

Pritom Mazumdar
Pritom Mazumdar

Posted on

Does your Razorpay webhook credit the same payment twice? (50 second demo)

Razorpay delivers webhooks at least once. If your endpoint is slow or a response gets lost, the same payment.captured arrives again, with the same X-Razorpay-Event-Id. A handler that does wallet += amount returns 200 every time and credits every time.

The demo above is a real run, not a mockup:

  1. A 20 line Flask handler that checks the signature and credits a wallet.
  2. carbon-layer sends 1,000 signed payment.captured webhooks, each delivered 3 times.
  3. --verify-cmd counts the credits before and after: 1,000 unique events, 3,000 credits. FAIL.
  4. Store the event id in a table with a unique key, in the same transaction as the credit.
  5. Run it again: 1,000 events, 1,000 credits. PASS.
pip install carbon-layer
carbon run flash-sale --provider razorpay \
  --webhook-url http://localhost:8000/webhooks --webhook-secret $SECRET \
  --webhook-repeat 3 \
  --verify-cmd "psql -tAc 'select count(*) from wallet_credits'"
Enter fullscreen mode Exit fullscreen mode

One more case, from a comment on my last post: a lot of apps also credit the wallet in their client side "verify payment" endpoint. Deduping on the event id won't catch that, because the webhook and the verify call are two different requests for the same payment. Since 0.9.0, --client-verify-url sends both and checks the wallet moved once per payment. The fix there is a unique key on the effect itself, one credit row per payment id.

No gateway account needed, works with Razorpay, Stripe, Cashfree, Juspay and Hyperswitch webhook formats.

Repo: https://github.com/Pritom14/carbon-layer
The scan of ~150 public handlers that started this: https://dev.to/pritom14/i-read-150-public-razorpay-webhook-handlers-at-least-9-would-process-the-same-payment-twice-1bnb

Top comments (0)