WooCommerce order stuck on "Pending payment": how to tell paid from unpaid, and fix the cause
An order is "Pending payment" until WooCommerce is told the payment succeeded. A stuck one was either never paid, which is normal, or was paid and the payment provider's notification never reached your site. The provider's own record of the payment tells you which.
- By
- WP Ministry
- Published
- Tested on
- WordPress 7.1.3, WooCommerce 11.2.0, PHP 8.3.35
In short
- Look the order up at the payment provider before anything else. A successful payment there is the only proof that it was paid.
- No payment at the provider means an abandoned checkout. WooCommerce cancels those itself when "Hold stock (minutes)" has a value and stock management is on.
- A blank "Hold stock (minutes)" field means unpaid orders are never canceled.
- A paid order that stays pending means the provider's notification did not arrive. The provider's delivery log shows the answer your site gave.
- The time limit cancels paid orders too when the notification was lost, so check recent "Cancelled" orders against the provider as well.
- Never ship on the customer's word alone. Confirm the payment, then set the order to "Processing".
"Pending payment" is the status WooCommerce gives an order from the moment the customer places it until the store is told the payment succeeded. WooCommerce's own description is that the order has been received and no payment has been made. An order that stays there is one of two things: the customer never paid, or the customer paid and the news never reached your site.
Nothing is lost. The order, the customer and the products are intact. While the hold stock time runs, the items in the order are held back from other buyers. No "Processing order" email goes to the customer while an order is pending. What is at risk is money: shipping an order nobody paid for, or sitting on one somebody did.
This page is about orders that stay pending. An order whose payment was refused moves to "Failed": see WooCommerce payment gateway errors. If no order is created at all, see WooCommerce checkout not working.
Paid or never paid: check at the provider first
The store cannot answer this. It only knows what it was told. The payment provider, the company behind the payment method the customer chose, knows what happened to the money.
Under WooCommerce, then Orders, open the order and note its number, total, time, payment method and the customer's email. Then log in to that provider's dashboard and look for the payment. A successful payment for the full amount, not refunded, means the order was paid. No payment, or one that failed or was never finished, means it was not.
| At the provider | In WooCommerce | What it is | Fix |
|---|---|---|---|
| No payment | "Pending payment", with no note about a payment | An abandoned checkout. This is normal. | Let WooCommerce cancel unpaid orders, then get the schedule running |
| A successful payment | "Pending payment", or "Cancelled" with the note "Unpaid order cancelled - time limit reached." | Paid, and the store was never told | The three notification fixes, then settle the orders |
| Either | The order was added by hand or by another system, or its notes show the status was moved back | Made or changed outside the checkout | Read the order's history |
For a paid order, one more reading narrows it down. The provider keeps a log of each notification it sent and the answer your site gave.
- No delivery attempt, or one sent to another address: check the notification at the provider.
- An answer of 401, 403, 404, 503, or a redirect (301, 302): your site turned it away. See what your site answers at the notification address.
- An answer of 500: PHP failed while handling it. Find the PHP error.
Where it goes wrong
A page request passes through each of these in turn. This one comes from a plugin.
- Browser
- DNS
- HTTPS
- CDN or firewall
- Web server
- PHP
- WordPress (this error comes from here)
- Database and files
What causes it
The customer never paid
CommonWooCommerce creates the order before the payment is taken. A customer who closes the provider's page, or whose card is not accepted there, leaves an order behind. WooCommerce cancels it after the "Hold stock (minutes)" time, unless that field is blank or stock management is off.
The scheduled cancellation is not running
SometimesUnpaid orders are canceled by a scheduled action, and scheduled actions are started by WP-Cron. If WP-Cron is switched off or cannot reach the site, the action waits and the unpaid orders stay.
The provider's notification goes to the wrong place or fails its check
CommonAfter a payment the provider sends your site a notification, called a webhook or an IPN. If none is set up for this site and this mode, if it points at an old address, or if its signing secret does not match, the customer has paid and the store is never told.
Fix: Check the notification at the provider, or Settle the orders that are already stuck
Your site turns the notification away
CommonThe provider sends to the right address and something answers before the gateway can. A firewall or security plugin refuses the request, a password or a maintenance page stands in front of the site, or the address redirects.
Fix: See what your site answers at the notification address, or Settle the orders that are already stuck
The notification arrives and PHP fails while handling it
SometimesThe gateway receives the notification and code in another plugin, or in the gateway itself, stops with an error before the order is saved as paid. The provider is answered with a 500.
Fix: Find the PHP error that stops the notification, or Settle the orders that are already stuck
The order was made or changed outside the checkout
RareAn order added by hand in the dashboard, or created by another system, starts as "Pending payment" and is never canceled by the time limit. A person or a plugin can also set a paid order back to "Pending payment".
How to fix it
Let WooCommerce cancel unpaid orders
- Easy
- Low risk
- About 10 minutes
- Steps tested on WordPress 7.1.3
Step 1: Open the inventory settings
Go to WooCommerce, then Settings, then Products, then Inventory. Two settings decide whether unpaid orders are canceled.
- Manage stock, with the checkbox "Enable stock management". With it off, the next field is hidden and nothing is canceled.
- Hold stock (minutes). Its description reads: "Hold stock (for unpaid orders) for x minutes. When this limit is reached, the pending order will be cancelled. Leave blank to disable." A new store has 60.
With WP-CLI, read both:
The first line should be
yesand the second a number. An empty second line means the field is blank, and unpaid orders are never canceled.bashwp option get woocommerce_manage_stock wp option get woocommerce_hold_stock_minutesStep 2: Set a limit and save
Tick the checkbox, type a number of minutes and press "Save changes". Use the screen for this and not WP-CLI: saving here also puts the cancellation on WooCommerce's schedule. Leave customers time to finish paying. The limit counts from the last change to the order.
Step 3: Confirm that it is scheduled
One row should come back, with a date no further ahead than the limit you set. Dates are in UTC.
bashwp action-scheduler action list --hook=woocommerce_cancel_unpaid_orders --status=pending --fields=id,hook,status,scheduled_dateStep 4: Know what a canceled order looks like
When the time is up the order moves to "Cancelled" with the note "Unpaid order cancelled - time limit reached.", its held stock is released, and the store receives the "Cancelled order" email. Only orders that came through the checkout are canceled this way.
To undo it: Set the field back to its old value and save.
Get the scheduled cancellation running
- Takes care
- Low risk
- About 20 minutes
- Steps tested on WordPress 7.1.3
Step 1: See whether the cancellation is late
Run the list command from the fix above, or go to WooCommerce, then Status, then Scheduled Actions and search for
woocommerce_cancel_unpaid_orders. The guide to WooCommerce's database update describes that screen. A pending row whose date has passed means the queue is not being run. No row at all means the limit is blank or stock management is off: use the fix above.Step 2: Run what is due
This cancels every "Pending payment" order from the checkout that is older than the limit, paid or not. Check the ones customers have asked about at the provider first.
It prints "Completed processing action" with the hook's name. Without WP-CLI, choose "Run" under the row on the Scheduled Actions screen.
bashwp action-scheduler run --hooks=woocommerce_cancel_unpaid_ordersStep 3: Find out why it did not run by itself
Action Scheduler's queue is started by WP-Cron, and WP-Cron runs only when a page is loaded and the site can send a request to itself.
"Success: WP-Cron spawning is working as expected." rules this out. An error that names
DISABLE_WP_CRON, an HTTP status or a connection failure is the cause. Each has its fix on the page about the "Missed schedule" error, which is the same fault seen on a scheduled post.bashwp cron testStep 4: Watch the next one
Once the test reports success, the next row should complete within a minute or two of its date, as long as pages are being loaded.
Check the notification at the provider
- Takes care
- Low risk
- About 30 minutes
Step 1: Find the address your gateway listens at
Go to WooCommerce, then Settings, then Payments, and open the gateway's settings. Most show the notification address or the state of the connection. Two examples from the extensions' own documentation: the Stripe extension listens at
https://example.com/?wc-api=wc_stripe, sets its webhooks up when you connect the account, and has a "Reconfigure webhooks" button. PayPal Payments has a "Webhooks Status" section with "Resubscribe" and "Simulate" buttons.Step 2: Open the provider's delivery log
Find the notification for a stuck order's payment. At Stripe, the "Event deliveries" tab of the webhook endpoint lists each event as Delivered, Pending or Failed, with the HTTP status code your site answered. PayPal's IPN history page does the same for IPN messages.
Step 3: If nothing was sent
No notification is set up for this site in this mode. Use the gateway's own button to set it up again, or add the address at the provider as the gateway's documentation describes.
Step 4: If it went to another address
An old domain, a staging copy,
httpwhere the site useshttps, orwwwwhere the site has none. Correct it to the address the site answers at now.Step 5: Check the mode and the secret
Test and live are separate. Stripe's documentation says each endpoint has its own signing secret, and that the secret differs between test and live even for the same address. A gateway holding the wrong one cannot check the signature and rejects the notification. Copy the secret for the live endpoint again, or reconnect the gateway.
See what your site answers at the notification address
- Takes care
- Low risk
- About 30 minutes
- Steps tested on WordPress 7.1.3
Step 1: Ask for the address yourself
Send an empty request to the address from the gateway's settings or the provider's log. Run it in a terminal on your own computer, so that it passes whatever stands in front of the site. The quoting is for macOS and Linux.
It prints the status code, and the address it was sent on to if there is one.
bashcurl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' -d '' 'https://example.com/?wc-api=wc_stripe'Step 2: Read the answer
Answer Meaning Where to look 403 A firewall, a security plugin or a server rule refuses the request 403 Forbidden 401 The whole site is behind a password, as a staging copy often is Leave the notification address out of the password rule, or remove it 301 or 302, and an address The address redirects. Stripe's documentation says it counts a redirect as a failed delivery Give the provider the address printed after the code 503 A maintenance page answers for the whole site Briefly unavailable for scheduled maintenance, or the maintenance plugin's settings 404 Nothing answers there The address is wrong, or the gateway plugin is not active 400, or 200 WooCommerce or the gateway answered. Your empty request names no payment, so a refusal here is expected. On a ?wc-api=address, 400 is also the answer when no active gateway goes by that nameYour site is not turning requests away. Go back to the provider's log WooCommerce's own "Coming soon" mode is not in this list. It replaces pages for visitors and leaves these addresses answering.
Step 3: For a 403 on Apache, look for a rule
Copy
.htaccesssomewhere outside the site's folder, then remove the rule that refuses the address. If nothing is printed, look in the security plugin's or the host's firewall log at the time of a failed delivery, and allow the notification address there. Stripe's documentation says its webhooks come from a published list of IP addresses.bashgrep -n -i -E 'wc-api|wp-json|denied|deny from' .htaccessStep 4: Ask again
Run the first command again. An answer of 400 or 200 means the request now reaches WooCommerce.
Step 5: Have the provider send one again
Your own request does not come from the provider's servers. A firewall that decides by address, by country or by the kind of client can let you through and still refuse the provider. The provider's delivery log has the last word: resend one failed notification from there and watch the order move to "Processing". If changing an address leaves the site redirecting in a circle, see ERR_TOO_MANY_REDIRECTS.
To undo it: Put back the rule or the setting you changed.
Find the PHP error that stops the notification
- Takes care
- Low risk
- About 20 minutes
- Steps tested on WordPress 7.1.3
Step 1: Read WooCommerce's fatal error log
Go to WooCommerce, then Status, then Logs, and open the newest
fatal-errorslog. WooCommerce keeps that log without being asked. Or read it from the site's folder:Look for an entry at the time of a failed delivery. "No such file" means WooCommerce has logged no fatal error. Then turn on logging in the gateway's own settings, if it has the option, and read its log on the same screen after the next payment.
bashgrep -h "CRITICAL" wp-content/uploads/wc-logs/fatal-errors-*.log | tail -n 3 | cut -c 1-300Step 2: Read the path in the entry
The entry names the file that failed. The folder under
wp-content/plugins/is the plugin at fault.Step 3: Switch that plugin off
Whatever that plugin does for the store stops while it is off. If the path names the gateway itself, do not switch it off: update it and send the error to its author.
Update the plugin, or leave it off until its author has fixed the error. The critical error page covers switching a plugin off without WP-CLI.
bashwp plugin deactivate plugin-folder-nameStep 4: Have the provider send the notification again
Resend it from the provider's delivery log. The answer should now be 200 and the order should move to "Processing". The resend is the provider's side.
To undo it: Activate the plugin again.
Read the order's history, then settle it by hand
- Easy
- Low risk
- About 10 minutes
- Steps tested on WordPress 7.1.3
Step 1: See where the order came from
Replace 123 with the order's number.
--user=1runs the command as the user with ID 1, usually the first administrator. WooCommerce's commands refuse to run without a user who may manage orders.created_viasays how the order was made.checkoutandstore-apiare the checkout.adminis "Add order" in the dashboard, andrest-apiis another system writing through WooCommerce's API. Only the first two are canceled by the time limit. Adate_paidor atransaction_idon a pending order means it was paid once and then set back.bashwp wc shop_order get 123 --user=1 --fields=id,status,created_via,date_created,date_modified,date_paid,transaction_idStep 2: Read its notes
The "Order notes" panel on the order screen records every change of status, newest first. A change made by a person on that screen has "by" and their name under it. A note WooCommerce or a plugin wrote by itself has none. With WP-CLI:
A line such as "Order status changed from Processing to Pending payment." gives the time. Ask the person named, or look at what a plugin that manages order statuses did at that minute.
bashwp wc order_note list 123 --user=1 --fields=date_created,noteStep 3: Settle it
A paid order goes back to "Processing", as in the last fix. An unpaid one nobody is waiting for is canceled on the order screen, or with:
bashwp wc shop_order update 123 --status=cancelled --user=1
To undo it: Set the order back to its earlier status.
Settle the orders that are already stuck
- Easy
- Low risk
- About 30 minutes
- Steps tested on WordPress 7.1.3
Step 1: List them
On the Orders screen, choose "Pending payment" above the list. Or:
Add the "Cancelled" orders with the time-limit note from the same days.
bashwp wc shop_order list --status=pending --user=1 --fields=id,date_created,total,payment_method_title,created_viaStep 2: Check each one at the provider
Do not ship on a customer's word, a screenshot or a forwarded receipt. An order counts as paid when the provider's dashboard shows a successful payment for it.
Step 3: Prefer a resend
Once the cause is fixed, have the provider send the notification again. The gateway then records the payment as it would have, transaction ID included. Stripe's documentation says it retries failed deliveries for up to three days in live mode, and that an event can be resent from the Dashboard for up to 15 days. PayPal's IPN history has a "Resend selected" button.
Step 4: Otherwise set the status by hand
Open the order, choose "Processing" under Status and press "Update". Or:
Stock is reduced, the store gets the "New order" email, the customer gets the "Processing order" email, and the paid date is set to now, as the notification would have done. It works the same on an order the time limit canceled. The provider's transaction ID is not recorded, so add it to the order as a private note.
bashwp wc shop_order update 123 --status=processing --user=1Step 5: Cancel the ones that were never paid
Leave them to the time limit, or cancel them on the order screen.
To undo it: Set the order back to its earlier status. Emails already sent cannot be recalled.
When to get help
Get help when the provider's log shows notifications delivered with a 200 and the orders still stay pending, or when the answer is an error you cannot trace to a rule, a plugin or a setting. What is left is the gateway's own log read line by line beside the provider's record. On a store, do not wait. Each hour adds customers who have paid for orders nobody is shipping.
Common questions
A customer says they paid, but the order says "Pending payment". Should I ship it?
Not yet. Look for the payment in your payment provider's dashboard. If it is there and succeeded, set the order to "Processing" and ship. If it is not there, the customer did not finish paying, whatever their screen showed.
How long does an order stay on "Pending payment"?
Until the store is told it was paid, or until the "Hold stock (minutes)" limit runs out and WooCommerce cancels it. A new store's limit is 60 minutes. If the field is blank or stock management is off, the order stays pending until someone changes it.
Why was an order canceled when the customer had paid?
The provider's notification did not arrive before the hold stock limit ran out. To WooCommerce the order looked unpaid, so it was canceled with the note "Unpaid order cancelled - time limit reached." Confirm the payment at the provider, set the order to "Processing", and then find out why the notification failed.
Is "On hold" the same thing?
No. WooCommerce's documentation describes "On hold" as awaiting payment confirmation, with stock already reduced: the status a bank transfer or a check gets while you wait for the money. The hold stock limit applies only to "Pending payment" and never cancels an order that is on hold.
How do I keep this from happening during a sale?
Beforehand, open the provider's delivery log and confirm that its latest notifications were delivered. During the sale, watch that paid orders reach "Processing". The sale readiness checklist lists the other checks, and the checkout runbook covers a checkout that stops taking payments. The failed orders calculator puts a figure on orders that were started and never paid. WP Ministry's WooCommerce maintenance service includes a checkout and payment test after every update. It is not constant monitoring of your payment provider.
- Glossary termAction Scheduler
- GuideHow to maintain a WooCommerce store: before every update, every week, every month and before a sale
- GuideHow to migrate from Shopify to WooCommerce
- GuideHow to set up WooCommerce email notifications
- GuideHow to speed up a WooCommerce store
- GuideHow to update WooCommerce safely: before, on staging, on the live store, and if it breaks

