The customer's bank statement shows the charge. Stripe or PayPal shows the payment as successful. But in WooCommerce, the order just sits there in Pending payment — no confirmation email, no stock deduction, no order in the fulfillment queue. If you've been staring at that mismatch wondering how a "successful" payment produces a "failed" order, the answer is almost always a webhook that never arrived.
Symptom: Payment Succeeds, Order Doesn't Update
This isn't a checkout crash. The customer completes payment fine and usually lands on a thank-you page. The problem shows up afterward:
- Order status stays on Pending payment or On hold indefinitely.
- Stripe/PayPal dashboard shows the charge as captured or completed.
- No order confirmation email goes out, so the customer emails you asking where their receipt is.
- Manually refreshing the order page in wp-admin doesn't fix it — the status truly never changed server-side.
- It happens intermittently, not on every order.
WooCommerce doesn't learn "the payment succeeded" by asking the customer's browser. For most gateways, the definitive confirmation comes from a separate, asynchronous HTTP request the payment processor's servers send directly to your site — a webhook (Stripe, Razorpay) or IPN (PayPal's older mechanism). If that request never reaches WooCommerce, or reaches it and gets rejected, the order is frozen in whatever state the browser redirect left it in.
Cause 1: Your Firewall or ModSecurity Is Blocking the Gateway's IPs
This is the most common cause on shared hosting and cPanel-managed VPS boxes. Stripe and PayPal send webhook requests from their own server ranges, not from a browser, and those requests often get flagged as suspicious by security software that doesn't recognize the user-agent or request pattern.
cPanel's ModSecurity, CSF/LFD, or an aggressive WAF rule can silently return a 403 to the webhook POST before it ever reaches WordPress. From the gateway's side this looks like a failed delivery attempt; from your side, nothing shows up in the WooCommerce logs at all because WordPress never got the request.
Check your Apache/Nginx access log for the webhook endpoint around the time of a failed order:
grep "wc-api" /usr/local/apache/domlogs/yourdomain.com 2>/dev/null | tail -30
# or on Nginx
grep "wc-api" /var/log/nginx/yourdomain.com.access.log | tail -30
If you see the request hit with a 403 or 406 status, that's ModSecurity or the firewall talking. In WHM, check ModSecurity Tools > Hits for entries around that URL and timestamp, and either whitelist the specific rule ID for the webhook path or add an exception for /?wc-api= or /wp-json/wc/ depending on which webhook URL format the gateway is using.
Cause 2: Wrong Webhook URL or Mismatched Signing Secret
The second most common cause has nothing to do with the server — it's a configuration mismatch between WooCommerce and the payment processor's dashboard.
- Wrong endpoint URL: if the site moved domains, went from staging to live, or switched from HTTP to HTTPS, the webhook URL registered in the Stripe/PayPal dashboard may still point at the old address.
- Signing secret mismatch: Stripe signs every webhook payload and WooCommerce verifies that signature using a secret key stored in the gateway's settings. If you regenerated API keys or copied the wrong secret, WooCommerce receives the webhook but rejects it as invalid and silently drops it.
- Test vs live mode mismatch: a webhook endpoint registered for test-mode events won't fire for live transactions, and vice versa.
In the Stripe dashboard, go to Developers > Webhooks, click the endpoint tied to your store, and look at the delivery log. A string of 400 or 401 responses next to specific events means WooCommerce is receiving the request but rejecting it — that's a secret-key problem, not a firewall problem.
Cause 3: The Webhook Never Left the Gateway's Queue
Occasionally the payment processor itself gives up. Stripe retries a failed webhook delivery with exponential backoff for up to three days, then stops. PayPal's IPN retries for a shorter window. If your site was down, mid-deploy, or returning 500 errors during that entire retry window, the webhook eventually gets abandoned and the order is stuck for good unless you intervene manually.
Fix: Diagnose, Then Resync the Order
| Check | Where to Look | What It Tells You |
|---|---|---|
| WooCommerce > Status > Logs | wp-admin > WooCommerce > Status > Logs tab | Gateway-specific log (e.g. woocommerce-gateway-stripe) shows if the request arrived and what WooCommerce did with it |
| Gateway dashboard webhook log | Stripe/PayPal developer dashboard | Shows delivery attempts, HTTP response codes, and retry history from their side |
| Server access log | cPanel > Metrics > Raw Access / SSH | Confirms whether the request reached your server at all, and with what status code |
| ModSecurity audit log | WHM > ModSecurity Tools > Hits | Shows if a WAF rule intercepted the request before WordPress saw it |
Once you know the cause:
- If it was a firewall block: whitelist the gateway's documented webhook IP ranges (Stripe and PayPal both publish these) or exclude the specific webhook URL path from ModSecurity/WAF inspection, then use the "resend" button in the gateway's dashboard to redeliver the missed event.
- If it was a secret mismatch: copy the correct signing secret from the gateway dashboard into WooCommerce > Settings > Payments > [Gateway] > webhook settings, save, and resend the failed event.
- If the retry window already expired: most gateways won't redeliver after that. Verify the payment manually in the gateway dashboard, then update the order status by hand in wp-admin and add an order note referencing the payment/transaction ID for your records.
Don't just click "Pending payment → Completed" blind. Confirm the charge actually settled on the gateway's side first — a customer's card can be authorized and then later declined or reversed, and you don't want to ship product against a payment that never actually cleared.
Prevention: Stop This From Happening Again
- Ask your host (or check in WHM yourself) to permanently whitelist your payment gateway's official webhook IP ranges in ModSecurity/CSF instead of relying on ad hoc exceptions.
- After any domain change, SSL change, or API key rotation, immediately re-verify the webhook URL and test it with the gateway's built-in "send test event" tool.
- Set up a simple uptime monitor on your webhook endpoint URL itself, not just your homepage — a 500 error there can hide behind an otherwise-healthy site.
- Enable WooCommerce's built-in logging for your payment gateway before you need it, so the log already has history when a customer reports a missing order.
- Review the gateway's webhook delivery dashboard monthly. A pattern of repeated retries on the same event is an early warning sign, not just noise.
Frequently Asked Questions
Why does the customer get a confirmation email from Stripe/PayPal but not from WooCommerce?
Those are two separate systems. The gateway's email confirms the charge on their end. WooCommerce's order confirmation email only fires after the webhook updates the order status — if that webhook never arrives, WooCommerce has no idea the payment happened, so it never sends anything.
Is it safe to just mark the order "Completed" manually?
Only after you've confirmed the payment actually settled in the gateway's dashboard. Manually completing an order without that check risks releasing a product against a payment that later fails, gets disputed, or was never fully captured.
Can I test whether my webhook endpoint is reachable right now?
Yes — both Stripe and PayPal dashboards have a "send test webhook" option that fires a sample event at your configured URL and shows you the response code your server returned, without needing a real transaction.
Does this affect Razorpay, PayU, or other gateways too?
Yes. Any gateway that relies on an asynchronous server-to-server callback to confirm payment (rather than only trusting the browser redirect) has the same failure mode. The names differ — webhook, IPN, callback URL — but the diagnosis steps are the same.
Why does this only happen to some orders and not others?
Intermittent WAF blocking is common — a firewall rule might only trigger on certain payload sizes or patterns, or the gateway's outbound IP can rotate across a range where only some addresses are blocked. That's why checking both the server access log and the gateway's delivery log matters; a single missed request rarely tells the whole story.
