Guest checkout is good for conversion and terrible for everything that happens after the sale. Your guest customers have no account, so they have no orders page, so the only route they have to find out what is happening is you.
There is a fix, and there is a way of building that fix which quietly turns your store into a customer-data lookup service. This guide covers both.
WooCommerce ships a shortcode for exactly this:
[woocommerce_order_tracking]
Put it on a page. The customer enters an order number and the billing email, and if both match they see the order’s status and items. The implementation is in WC_Shortcode_Order_Tracking, and it is worth knowing exactly what it checks — it loads the order, then compares the submitted email against $order->get_billing_email(), lowercased on both sides. No match, no order.
That is a real security control and it is already there. Before you install anything, put this shortcode on a page and link it from your footer. It costs nothing.
None of that makes it useless. It makes it a floor, not a ceiling.
Here is the part that matters, and the reason this post exists.
WooCommerce order IDs are sequential. Order 1041 is followed by order 1042. Anyone who has ever bought from you knows one valid order number, and can therefore guess several thousand more.
Now consider the lookup form somebody builds when they decide core’s shortcode is too plain. It takes an order number. It looks the order up. If it finds one, it shows the order. If it does not, it says “order not found”.
That form is an enumeration oracle. A script walks the number range and gets back, for each valid order, whatever the page renders — which on most implementations is the customer’s name, their delivery address, what they bought and what they paid. Nobody has to break anything. The form is working exactly as built.
The subtler version has an email field but leaks anyway. If a wrong email returns “that email does not match this order” and a non-existent order returns “order not found”, the two different messages tell an attacker which order numbers are real. Distinguishable responses are the leak, even when the data itself is protected.
Four things, and you need all four.
An order number on its own is never sufficient. Compare after normalising — trim and lowercase both sides — and compare with hash_equals() rather than === so the comparison does not leak timing information.
Wrong email, non-existent order, right order but a typo — all three produce the same page. The pattern that works well is to make the response a non-answer that is also useful:
If that order exists, we have emailed a secure link to the address on file for it.
That sentence is true whether or not the order exists. It reveals nothing. And when the order does exist, the email genuinely goes out — so the legitimate customer gets what they came for and the ticket is still deflected.

Per IP is the obvious one and the easiest to work around. Limit per IP, per email address, and per site. Something in the region of five attempts per quarter hour per IP and ten per hour per email is strict enough to make enumeration pointless and loose enough that a confused customer never notices.
Store the counters in transients or the object cache, not in wp_options. Options have no expiry and autoload by default, so a rate limiter built on them slowly poisons every page load on the site.
Never to the address typed into the form. If a submitted address can receive the link, your form is a mail-bomb tool with your domain in the From header, and the first person to notice will be your deliverability provider.
A lookup form is a fallback for a customer who has lost their email. The main route should be a link in the transactional email they already received.
A signed link carries a token that identifies the order and proves the link came from you. A reasonable construction:
payload = order_id | order_key | expiry
token = base64url( payload ) . '.' . hash_hmac( 'sha256', payload, $secret )
Four rules make this safe.
hash_equals(). A plain string comparison on an HMAC is a timing oracle.A signed link must grant access to one order. It must never call wp_set_auth_cookie() or otherwise create a WordPress session.
Magic-login links look convenient and are an account-takeover primitive. That URL will end up in browser history on a shared laptop, in a forwarded email, in a referrer header, in a proxy log. If it grants an order view, the worst case is that somebody sees one order. If it grants a session, the worst case is that somebody has the customer’s account, their address book and their saved payment methods.
The related habit: get the token out of the URL as soon as it has been used. Land on the token, exchange it for a short-lived cookie-bound context, redirect to a clean URL. After that first hop the token is not in the address bar, not in the referrer sent to any third-party script on the page, and not in your access logs.

This one is not theoretical, and it is the reason to test on a staging site that has your real caching stack on it.
A guest order page has no login cookie. To a page cache it is an anonymous page like any other, so it gets cached — and the next anonymous visitor to that URL is served the previous customer’s name, address and order contents from cache.
Three defences, and use all of them:
wc_nocache_headers() on every order-bearing response, and define DONOTCACHEPAGE — most WordPress caching plugins honour it.Cache-Control: private, no-store explicitly. A CDN in front of your host has never heard of your caching plugin’s constant.Test it properly: place a guest order, open its link, then open the same URL in a private window from a different network. If you see the order, you have a leak.
Working setups tend to have all four of these:
And once a guest can see their order, the obvious next question is what they can do with it — cancelling and the wider guest post-purchase gap are where that goes next.
Everything above is about access, not information. A guest who successfully finds their order still only learns what your order data knows. If your statuses are updated in a Friday batch and you have no tracking numbers, a guest lookup shows them a vague answer faster — which is an improvement, but a smaller one than it looks.
Fix the underlying information first, then make it reachable.