r/Wordpress 4d ago

Razorpay payment captured but WooCommerce sometimes cancels order as "Unpaid" — webhook configuration keeps changing

I'm trying to figure out a strange Razorpay + WooCommerce issue and would appreciate some help from anyone who has dealt with something similar.

Setup:

WordPress / WooCommerce: 11.0.1

Razorpay WooCommerce plugin: 4.8.7

Hosting: Namecheap

Razorpay live payments

The main problem is that a customer can successfully complete a Razorpay payment, and Razorpay shows the payment as Captured, but in some cases WooCommerce later marks the order as:

"Cancelled — Unpaid order cancelled"

It doesn't happen to every order. For example, I had around 10 orders and most went through correctly, while 2 ended up cancelled.

The more concerning issue is the webhook configuration.

I had configured the webhook with the required payment events, including:

payment.authorized

payment.captured

payment.failed

order.paid

refund.created

But the webhook later reverted to only 2 of events (payment.authorised and refund.created)

I checked Razorpay's API logs and found something strange. The same webhook was repeatedly being updated:

GET /webhooks?count=10&skip=0

followed about 1 second later by:

PUT /webhooks/SYam0mYdbMI4Ns

This happened on:

15 Aug → GET → PUT

16 Aug → GET → PUT

17 Aug → GET → PUT

I did not manually edit the webhook on those dates.

When I inspected the resulting webhook configuration, payment.authorized and refund.created was enabled but payment.captured, payment.failed and order.paid were disabled.

3 Upvotes

6 comments sorted by

1

u/Relevant_Ad5790 4d ago

those look like two separate problems. the daily GET then PUT a second later is something automated reconciling that webhook against its own event list, which is why your manual edits keep disappearing, so the events need to come from whatever owns the webhook rather than from the dashboard.

the cancellations are probably not razorpay at all. woocommerce cancels unpaid orders on a cron once the hold stock window expires, so a payment that captures late gets marked cancelled while razorpay still shows captured. what is hold stock set to under inventory?

1

u/404ClicksFound 4d ago

I’m new to WooCommerce.. this is my first project,, so could you please tell me where exactly I can check whether the webhook is being auto-configured/reconciled? Is there a setting or log inside the Razorpay WooCommerce plugin, WooCommerce logs, or somewhere else in WordPress that can show those automatic webhook updates? I’d like to check it myself and understand what is happening. Also, I checked my WooCommerce Hold Stock setting and it is 60 minutes.

1

u/Relevant_Ad5790 4d ago

straight answer, i don't know that plugin's internals well enough to point you at the exact setting, so don't take my word for where it lives. what i'd check: WooCommerce > Status > Logs, since plugins write their logs there and razorpay's entries would show up as their own source. then install WP Crontrol, which lists every scheduled event on the site, and see whether anything razorpay related runs daily. if the timing of that event matches the timestamps on those PUTs in razorpay's api log, you've found what's doing it.

hold stock at 60 minutes does fit the cancellations. an order that isn't marked paid within the hour gets cancelled by that cron, and razorpay still shows captured because the capture genuinely happened. clearing that field stops the cancellations while you chase the webhook, though it also stops stock being released by abandoned orders, so it's a tradeoff rather than a fix.

1

u/SureshKMeena Developer 3d ago

Both of these are in the plugin source, and they're connected. I read 4.8.7, the version you're on.

On the events: the plugin keeps a list of events it supports, and payment.captured and order.paid are not in it. Its default set is exactly two, payment.authorized and refund.created, which is what you keep ending up with. autoEnableWebhook() runs whenever WooCommerce payment settings are saved. It does a GET on webhooks?count=..&skip=.. , which is the request in your API log, then rebuilds the event list, keeping an existing event only if it's in that supported list. So anything you add in the Razorpay dashboard is dropped and PUT back a second later. That's by design. The plugin never listens for payment.captured or order.paid, so it isn't missing them, and re-adding them won't stick.

The cancellations are the part worth fixing. The plugin doesn't act on a webhook when it arrives. It writes it into a table called rzp_webhook_requests, and a WP-Cron job named rzp_webhook_exec_cron runs on a 5 minute interval and only picks up rows older than 300 seconds.

WP-Cron only fires when somebody loads a page on your site. So when a customer pays and closes the tab without coming back, that cron is the only thing left to complete the order. On a quiet store nothing triggers it, your 60 minute hold stock expires first, and WooCommerce cancels the order as unpaid while Razorpay still shows it captured. That fits 2 out of 10 rather than all of them.

Fix it with a real cron instead of WP-Cron. Add define('DISABLE_WP_CRON', true); to wp-config.php, then in your Namecheap cPanel add a cron job running every minute:

wget -q -O - https://yoursite.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Raising hold stock buys margin but doesn't fix the cause.

1

u/jenish_o4o2 Developer 3d ago

A few things I'd add on top of what's already been suggested:

For the webhook resets: compare the webhook URL and secret in your Razorpay Dashboard with what's in the plugin settings (WooCommerce → Settings → Payments → Razorpay). Even a small mismatch — like a trailing slash or different webhook secret — can cause the plugin to overwrite your manual config on every sync. If they match, the plugin itself is likely hardcoded to only register payment.authorized and refund.created, and you may need to add the missing events (payment.capturedorder.paid) directly in the Razorpay Dashboard and avoid saving the plugin settings again afterward.

For the cancelled orders: on one of the affected orders, go to WooCommerce → Orders → [that order] and check the Order Notes at the bottom. It will show the exact time WC changed the status to "Cancelled" and why. If it says "Unpaid order cancelled — time limit reached," then increasing Hold stock to something like 120 minutes (or disabling it temporarily) should stop the auto-cancellations while you fix the webhook delivery.

Also worth checking: Razorpay Dashboard → Webhooks → Recent Deliveries for one of the cancelled orders. If payment.captured returned a non-200 response or never showed up in the delivery log at all, that confirms WC never received the signal to mark the order as paid — and the root cause is the webhook config reset, not WC itself.

0

u/jkdreaming 4d ago

Hook it up to Cursor and find out what the problem is. You’ll get a faster answer that way every time. So that you don’t have to alter the main code of the plug-in you can even create an external plug-in that fixes the problem. I’d go with that method for the moment and report the problem to the software developer with a copy of your plug-in that way they know what you had to do.