
It's Black Friday. Your WooCommerce store is taking hundreds of orders an hour, and every single order has to be synced to your ERP, invoiced in your accounting software, pushed to the warehouse and added to your CRM. Each step calls a slow external API. Soon requests start timing out, webhook deliveries fail, and after enough failures WooCommerce quietly disables the webhook. Orders stop syncing, and nobody notices until customers start asking where their parcels are.
This is exactly the kind of problem Laravel queues were built to solve. Instead of doing heavy work while WooCommerce waits, you accept the order notification instantly, put the real work on a queue and let background workers process it safely, with retries when something fails.
This guide walks through a practical setup for using Laravel queues to process WooCommerce orders: receiving and verifying order webhooks, designing jobs that never double-process an order, running workers reliably, protecting downstream systems and catching any orders that slip through.
Why Process WooCommerce Orders in Laravel at All?
WooCommerce is excellent at running a store. It's less suited to heavy back-office work. Syncing every order to several external systems inside WordPress slows down requests, competes with shoppers for server resources and becomes hard to monitor when something breaks.
Moving that work to a separate Laravel application gives you:
- Real background queues with workers you can scale up during busy periods
- Automatic retries with sensible delays when an external API is down
- Clear visibility into what failed, why and when
- A cleaner WordPress install that stays focused on running the store
Many businesses already run this kind of setup, with WordPress handling the storefront and content while Laravel handles custom business logic. If you're weighing that approach, this overview of a WordPress and Laravel hybrid stack explains when each tool fits best.
The Architecture in One Picture
Here's how the pieces fit together:
- A customer places an order, and WooCommerce sends an order webhook (order.created or order.updated) to your Laravel app.
- Laravel verifies the signature, saves the raw event, dispatches a job onto a queue and immediately returns a success response.
- Queue workers pick up the job, fetch the latest order details from the WooCommerce REST API and sync the order to your ERP, accounting, fulfillment and CRM systems.
- A scheduled reconciliation task regularly checks WooCommerce for recently changed orders and queues any that were missed.
The key idea is that the webhook endpoint does almost nothing. All the slow, failure-prone work happens later, in the background.
Step 1: Receive and Verify the Webhook
WooCommerce signs every webhook delivery, so you can be sure it really came from your store. According to Hookdeck's guide to WooCommerce webhooks, WooCommerce creates an HMAC-SHA256 signature of the request body using the secret you set when creating the webhook, base64-encodes it and sends it in the X-WC-Webhook-Signature header. You must verify it against the raw request body, exactly as received.
Here's a compact Laravel controller that verifies the signature, records the delivery and queues the work:
php
public function __invoke(Request $request) { $order = json_decode($request->getContent(), true); // WooCommerce sends a test ping when a webhook is saved: accept it, do nothing. if (! isset($order['id'])) { return response()->noContent(); } $expected = base64_encode(hash_hmac( 'sha256', $request->getContent(), config('services.woocommerce.webhook_secret'), true )); if (! hash_equals($expected, (string) $request->header('X-WC-Webhook-Signature'))) { abort(401); } $event = WooEvent::firstOrCreate( ['delivery_id' => $request->header('X-WC-Webhook-Delivery-ID')], ['order_id' => $order['id'], 'payload' => $request->getContent()] ); if ($event->wasRecentlyCreated) { ProcessWooOrder::dispatch((int) $order['id'])->onQueue('orders'); } return response()->noContent(); }
A few details matter here:
- Respond fast. WooCommerce treats any response other than a 2xx status, 301 or 302 as a failed delivery. After five consecutive failures, it disables the webhook, although developers can change that limit with a filter. A quick success response keeps your webhook healthy.
- Use hash_equals for the comparison, which avoids subtle timing attacks.
- Choose a simple secret. WooCommerce decodes special characters in the secret before signing, so an alphanumeric secret avoids mysterious signature mismatches.
- Record every delivery. Saving the delivery ID and raw payload gives you an audit trail and lets you ignore repeat deliveries of the same event.
Step 2: Design the Job
One Order, One Job, One Unique ID
Webhooks can be delivered more than once, and the same order can trigger several events in quick succession, such as created, then paid, then updated. Your job design has to handle that without processing the same order twice in parallel.
Laravel offers two tools that work well together. A unique job prevents duplicate copies of the same job from piling up in the queue. A WithoutOverlapping middleware, keyed by order ID, makes sure two jobs for the same order never run at the same time.
There's a subtle trap worth knowing. A standard unique job keeps its lock until it finishes, so an order.updated event that arrives while the first job is still running would be dropped. Using Laravel's "unique until processing" option releases the lock when the job starts, so a later update queues normally and simply waits its turn.
Make the Work Idempotent
Idempotent means running the job twice produces the same result as running it once. In practice:
- Fetch the latest order from the WooCommerce REST API inside the job, rather than trusting a possibly outdated webhook payload.
- Upsert records in other systems using the WooCommerce order ID as the key, instead of always creating new ones.
- Check before acting. Before sending an invoice or confirmation email, check whether one was already sent for that order.
Retries and Backoff
External APIs fail. Your job should retry a limited number of times, waiting longer between attempts, and alert someone when it finally gives up:
php
class ProcessWooOrder implements ShouldQueue, ShouldBeUniqueUntilProcessing { use Queueable; public int $tries = 5; public array $backoff = [30, 120, 600]; public function __construct(public int $orderId) {} public function uniqueId(): string { return (string) $this->orderId; } public function middleware(): array { return [(new WithoutOverlapping((string) $this->orderId))->releaseAfter(30)]; } public function handle(OrderSync $sync): void { $sync->syncOrder($this->orderId); } public function failed(?Throwable $e): void { Log::error('Woo order sync failed', ['order' => $this->orderId, 'error' => $e?->getMessage()]); } }
This job tries up to five times, waits 30 seconds, then two minutes, then ten minutes between attempts, and logs a clear error if every attempt fails. Laravel also supports a retryUntil method, which retries for a set period of time instead of a fixed number of attempts. Laravel's queue documentation for version 13 also shows a newer attribute-based syntax for settings like tries, while the property style above is still common in existing projects.
Step 3: Run Workers Properly
Queue jobs only run when workers are running. A few practical choices make a big difference:
- Pick a queue driver. Laravel supports database, Redis, Amazon SQS and Beanstalkd queues. The database driver is simplest to start with, and Redis handles high order volumes better.
- Separate queues by priority. Put order syncing on an orders queue and less urgent work, like marketing emails, on default, then run workers that process the important queue first.
- Keep workers alive. Run php artisan queue:work under a process manager such as Supervisor, so workers restart automatically if they crash or time out.
- Set sensible timeouts so a hung API call doesn't block a worker forever.
- Restart workers after every deploy, so they load your latest code.
If you use Redis, Laravel Horizon adds a dashboard showing queue depth, throughput and failed jobs, which is especially helpful during sales events.
Step 4: Protect Downstream Systems
Your ERP, shipping provider or accounting software may limit how many requests you can make per minute. If a flood of orders arrives at once, your workers can overwhelm them.
Laravel's queue middleware helps:
- Rate-limited middleware caps how often jobs call a particular service, releasing extra jobs back onto the queue to try later.
- WithoutOverlapping keeps work for the same order in sequence, as described above.
- Job batching groups bulk tasks, such as end-of-day invoicing, with callbacks that run when the whole batch succeeds or when something fails.
These tools let your system absorb a traffic spike gracefully, processing orders steadily instead of hammering every connected service at once.
Step 5: Handle Failures and Missed Orders
Even a well-built system will occasionally miss something. Plan for it:
- Review failed jobs. Laravel records failed jobs so you can inspect the error, fix the cause and retry them. Set an alert if failures spike.
- Reconcile regularly. A scheduled task can ask the WooCommerce REST API for orders modified since the last run, compare them with what Laravel has processed, and queue anything missing. This catches orders from webhooks that were never delivered, or from a period when the webhook was disabled.
- Keep raw payloads. Stored webhook events let you replay processing after a bug fix and answer questions about what happened and when.
Reconciliation is the safety net that turns "we lost some orders" into "we caught them within the hour."
Monitoring Checklist
Keep an eye on these numbers, especially before and during busy periods:
- Queue depth, meaning how many jobs are waiting
- Average job runtime and failure rate
- Time from order placed to order synced
- Webhook status in WooCommerce, to confirm it's still active
- Alerts when the queue backs up or failures cross a threshold
Frequently Asked Questions
What are Laravel queues used for?
Laravel queues move slow or unreliable tasks, such as sending emails, calling external APIs or processing orders, out of the web request and into background jobs. Workers process those jobs separately, with retries and failure handling, which keeps your application fast and makes heavy workloads far more reliable.
Which queue driver should I use in Laravel?
The database driver is the easiest place to start and works well for modest volumes. Redis is faster and better suited to high-volume order processing, and it works with Laravel Horizon for monitoring. Amazon SQS is a good fit if your infrastructure already runs on AWS.
How do I verify a WooCommerce webhook signature?
WooCommerce sends an HMAC-SHA256 signature of the raw request body, base64-encoded, in the X-WC-Webhook-Signature header, using the secret you set when creating the webhook. In Laravel, compute the same signature from the raw body and compare the two with hash_equals.
Why did WooCommerce disable my webhook?
WooCommerce disables a webhook after five consecutive failed deliveries by default. A delivery counts as failed if your endpoint doesn't respond with a 2xx status, 301 or 302, often because it's too slow or throws an error. Respond quickly and move heavy work to a queue to prevent it.
How do I stop duplicate order processing in Laravel?
Combine several safeguards: record each webhook's delivery ID and ignore repeats, make jobs unique per order, use WithoutOverlapping so two jobs for the same order never run together, and make the work itself idempotent by upserting records and checking before sending invoices or emails.
Wrapping Up
A reliable WooCommerce order pipeline with Laravel queues comes down to a few habits. Verify every webhook, respond immediately, queue one unique and idempotent job per order, retry with sensible delays, protect the systems you connect to and reconcile regularly so nothing slips through. Do that, and your busiest sales day becomes just another day in the logs.
If you'd like this built, tested and monitored for your store, Khurram Shahzad develops custom Laravel integrations for WooCommerce, from webhook handling and queue design to ERP and fulfillment sync.
Sign in to leave a comment.