In short
Customers paid, but the order still says pending. A step-by-step way to tell lost payment messages from abandoned payments, and to find what blocks them.
A customer emails a screenshot of their bank app showing the payment went through. In WooCommerce the order still says Pending payment. You can't ship it, but you can't cancel it either, because the money may really be there.
To WooCommerce, "pending payment" means it has created the order and is still waiting to hear that it was paid. The status isn't an error. It means the store hasn't been told yet. So the useful question is: did the payment really succeed, and if it did, why did the message never arrive?
Below is the order we check things in when a store hands us this problem. Most of it you can do yourself in an afternoon with admin access to the store and to your payment provider's dashboard.
Step 1: Decide which kind of "pending" you have
Open the stuck order and look at two things before you change anything.
- The order notes (the column on the right of the order screen). A healthy card order usually has a note from the gateway saying the payment was completed, often with a transaction ID. WooCommerce's own order troubleshooting guide points out that missing notes can mean the gateway never answered the store at all.
- The same payment in the provider's dashboard (Stripe, PayPal, iyzico, Mollie or whichever you use). Search by the customer's email, the amount or the order number.
That gives you one of three answers:
- The provider shows the payment as successful, but the store says pending. This is the real "paid but pending" case. The provider's message didn't reach the store, or the store rejected it. Go to steps 2 to 4.
- The provider shows an unfinished or failed payment. The customer didn't complete the payment. Go to step 5.
- The provider has no record at all. The customer never got to the payment step, or the gateway didn't start properly. Turn on the gateway's logging (step 2) and place a test order.
Don't mark an order as Processing until the provider's dashboard confirms the money arrived. A manual status change ships goods. It doesn't collect payment.
Step 2: Check whether the payment message reached the store
Most card gateways don't rely on the customer's browser coming back to your site. The provider's server sends a separate message to your server, called a webhook (PayPal's older system calls it IPN, Instant Payment Notification). The order moves to Processing only when that message arrives and is accepted. If the customer closes the tab after paying, the webhook is what saves the order.
How to check it:
- Stripe: in the Stripe Dashboard, open the webhook endpoint for your store and look at its event deliveries. Each one shows as Delivered, Pending or Failed, with the HTTP status code your server sent back. The official WooCommerce Stripe plugin uses an endpoint in the format
https://yourstore.com/?wc-api=wc_stripe, and its settings (WooCommerce > Settings > Payments > Stripe, then Configure connection) should show the live webhook as "Configured". - PayPal Standard (the older built-in gateway): a paid order normally gets an "IPN Payment Complete" note. If it never appears on paid orders, the store isn't receiving IPN messages.
- PayPal Payments and other newer plugins: look for a webhook status section in the plugin's connection settings. It usually offers a way to re-register the webhooks.
- Your own logs: enable logging in the gateway's settings, then read WooCommerce > Status > Logs. Logging only records new transactions, so place a test order after switching it on. Turn verbose logging off again when you're done.
A very common finding is a webhook pointing at the wrong address: an old domain after a migration, a staging site, or http:// after the store moved to https://. Also check that the webhook was set up in live mode, not only in test mode.
One useful detail from the Stripe webhook documentation: in live mode Stripe keeps retrying a failed delivery for up to three days, and you can resend a single event from the dashboard for up to 15 days after it was created. Once you fix the cause, older stuck orders can often be recovered by resending their events instead of editing them by hand.
Step 3: Look for something blocking the message
If the provider's delivery log shows failures, the HTTP status code tells you where to look. Stripe's documentation lists what each code means. In WooCommerce stores, the usual causes are:
- A 403 (forbidden) or 401 response. Something refused the request before WooCommerce saw it. Common culprits are a security plugin that blocks unknown POST requests, a server firewall such as ModSecurity, Cloudflare rules or bot protection showing a challenge page to a machine that can't solve it, and password protection or a maintenance-mode page on the whole site.
- A 301 or 302 redirect. Stripe counts any redirect as a failed delivery. Redirect rules for
www, trailing slashes or a language prefix can catch the webhook URL. Register the exact final URL. - A cached response. If a page cache or CDN (content delivery network) answers the webhook URL from cache, the request never reaches WooCommerce. Exclude the gateway's endpoint, and any URL containing
wc-api, from caching. - A 500 error or a timeout. The message arrived, but PHP crashed or took too long to answer. The PHP error log and WooCommerce's fatal-errors log usually name the plugin involved. This often starts right after a plugin update.
- A TLS error. The SSL certificate is expired or incomplete. Stripe requires TLS 1.2 or higher.
To test after a change, resend one event from the provider's dashboard and watch for a 200 response and a new order note.
Step 4: Check that scheduled tasks are running
WooCommerce does a lot of work in the background: cancelling unpaid orders, sending some emails, and follow-up tasks that payment plugins schedule. This runs on WP-Cron, WordPress's built-in scheduler, and on Action Scheduler, the job queue WooCommerce ships with, which WP-Cron starts by default.
WP-Cron has a catch. It only runs when someone loads a page. If the site is quiet, if DISABLE_WP_CRON is set in wp-config.php with no real server cron job replacing it, or if a firewall blocks the site's request to its own wp-cron.php, the queue stops moving.
How to check it: open WooCommerce > Status > Scheduled Actions and filter by Pending. If you see actions whose scheduled time is well in the past, often with a "past-due" warning at the top of the admin, the queue isn't being run. The fix is usually a real server cron job that calls WP-Cron every few minutes, plus removing whatever blocks it. If it's stuck, this is also why unpaid orders never get cancelled automatically (see the next step).
Step 5: Separate abandoned payments from lost ones
Many pending orders are genuinely unpaid, and that's normal.
- 3D Secure dropped mid-way. 3D Secure is the extra check by the buyer's bank: a code by text message or a confirmation in the banking app. If the customer gives up, the bank declines, or the confirmation window fails to load on their phone, the order stays pending and the provider shows the payment as incomplete or failed. One or two of these a week is ordinary. A cluster of them on one browser or one type of phone points to a checkout problem, often a plugin or script that conflicts with the bank's pop-up.
- Customers who left. Someone reaches the payment page, then closes it. With the block-based checkout, WooCommerce holds the basket as a Draft and creates the pending order when the customer clicks Place Order. If they leave after that, you're left with a pending order.
- Automatic clean-up. Under WooCommerce > Settings > Products > Inventory, the "Hold stock (minutes)" setting cancels pending orders after that time and releases their stock. It only applies to Pending payment, never to On hold, and it needs scheduled tasks to run (step 4). If old pending orders pile up and never cancel, check both.
Step 6: Know which statuses your gateway uses
Some orders look stuck but are behaving exactly as designed:
- On hold means "waiting for you to confirm the payment". It's what bank transfer (BACS) and cheque orders get by default. Delayed methods such as SEPA Direct Debit can sit on hold until the money clears. Gateways that authorise a card first and capture it later can also use it.
- Failed means the payment was declined. With delayed methods, an order can wait on hold first and move to Failed later.
- Processing means paid and ready to ship. WooCommerce never moves it to Completed by itself; someone has to do that after shipping, unless every product is virtual and downloadable.
Check the gateway's documentation for which statuses it sets and when. A store that uses two payment plugins for the same method, or an old plugin left switched on next to its replacement, can end up with two systems both trying to change the same order.
Stop it happening quietly again
Whatever caused it, the lasting fix is the same: a daily check that compares what the provider says was paid against what the store recorded, and flags every difference for a person. Run it by hand from two exports if you have to. Most stores find out about this problem from an annoyed customer, sometimes days later. A daily check finds it the same morning.
Two habits help as well: test checkout with a real payment after every plugin or security change, and recheck webhook addresses after any domain, hosting or Cloudflare change.
Where we come in
This is the work behind our WooCommerce checkout fix. We follow real orders from basket to invoice, read the gateway and server logs, find where the payment message gets lost, and fix it in the store you already run, with no rebuild. We have built and run checkouts like this ourselves. Outdoora is a multi-vendor booking marketplace on WooCommerce that takes card payments with 3D Secure and bank transfer, runs behind Cloudflare, and gets server and payment-connection fixes from us as part of keeping it running. Alice & Grace is a WooCommerce store with iyzico card checkout that we've hosted and supported since its launch in 2019.
If you've worked through the steps above and orders still go missing, send us an example order and the gateway you use.
FAQ
Frequently asked questions
Tagged