TL;DR
- WooCommerce's built-in webhook auto-disables after repeated failures, locks you into a fixed payload format, and has no conditional dispatch or replay
- Webhook Actions queues every delivery via Action Scheduler, returning control to checkout immediately
- Use
fswa_webhook_payloadto enrich order data andfswa_should_dispatchto filter by status, total, or product - Failed deliveries retry with exponential backoff and can be bulk-replayed from the admin log without re-triggering WooCommerce hooks
/ Overview
Why do WooCommerce webhooks need a persistent queue?
WooCommerce ships with a built-in webhook system backed by Action Scheduler — deliveries are queued asynchronously just like Webhook Actions. The limitations are elsewhere: failed deliveries are retried a fixed number of times before the entire webhook is auto-disabled, the payload is locked to WooCommerce's own JSON schema, there is no per-delivery conditional logic, and bulk replay after an outage requires re-triggering the original WooCommerce events. High-volume stores — Black Friday traffic, bulk imports — frequently lose delivery windows when the downstream system is briefly unavailable and the built-in retry window is exhausted.
Action Scheduler solves the problem by decoupling delivery from the triggering request. The WooCommerce hook fires, a job is queued in the Action Scheduler database, and the queue runner delivers the payload asynchronously with full exponential-backoff retry and a queryable delivery log.
/ WooCommerce Hooks
Which WooCommerce hooks should trigger webhook deliveries?
Every major WooCommerce order lifecycle event exposes a WordPress action hook. The most useful for webhook automation are:
PHP — key WooCommerce order hooks
// New order created at checkout add_action( 'woocommerce_checkout_order_created', function( $order ) {} ); // Payment confirmed (works for all payment methods) add_action( 'woocommerce_payment_complete', function( $order_id ) {} ); // Any status transition — from pending to processing, etc. add_action( 'woocommerce_order_status_changed', function( $order_id, $old_status, $new_status, $order ) {}, 10, 4 ); // Specific transition hooks (cleaner for single-status targets) add_action( 'woocommerce_order_status_completed', function( $order_id ) {} ); add_action( 'woocommerce_order_status_refunded', function( $order_id ) {} );
WooCommerce's built-in webhook system already subscribes to a subset of these hooks. When using Webhook Actions, you bind the same hooks directly in wp-admin without writing PHP — the plugin handles the listener registration and queuing.
/ Hook Binding
How do you bind a WooCommerce hook to a webhook endpoint?
In Webhook Actions, open wp-admin → Webhook Actions → Add New. Set the Trigger to the WooCommerce hook name (e.g. woocommerce_payment_complete) and the Endpoint URL to your receiving system. The plugin registers the WordPress action listener automatically — no PHP required.
For woocommerce_order_status_changed (which passes four arguments), set the Args count field to 4 so the plugin captures all arguments. The payload mapper then exposes $order_id, $old_status, $new_status, and the $order object as payload fields.
If you need PHP-level control over the binding — per-environment endpoint URLs, runtime conditions — use the REST API to create the webhook programmatically or the fswa_webhook_url filter to rewrite the endpoint URL per delivery.
/ Queue Delivery
How does Action Scheduler queue WooCommerce webhook deliveries?
When the WooCommerce hook fires, Webhook Actions calls as_enqueue_async_action with the webhook ID and serialized trigger arguments as the action args. The current PHP request returns immediately. In the next queue-runner cycle, the action fires do_action() with those args, the plugin reconstructs the payload, and wp_remote_post delivers it to your endpoint.
The queue runner runs outside the WooCommerce checkout request. A 30-second API timeout on your CRM side will not slow down the customer's order confirmation — it will only delay retry processing in the background.
WooCommerce checkout → Webhook Actions → Action Scheduler
Customer WordPress / WooCommerce Queue Runner
│ │ │
│── POST /checkout ───────>│ │
│ │─ woocommerce_payment_complete │
│ │─ as_enqueue_async_action ───> DB (pending)
│<─── Order confirmed ─────│ │
│ │ ┌─── pulls batch │
│ │ │ wp_remote_post(CRM)
│ │ │ 5xx → retry w/ backoff
│ │ └─── log attempt / Payload Mapping
How do you map WooCommerce order data to a webhook payload?
Webhook Actions automatically serializes the hook arguments into the base payload. For woocommerce_payment_complete, the payload contains $order_id as the root value. For richer payloads — order total, line items, billing email — use the fswa_webhook_payload filter to enrich the outgoing payload just before dispatch.
PHP — enrich WooCommerce webhook payload
add_filter( 'fswa_webhook_payload', function( $payload, $webhook_id, $trigger ) { if ( $trigger !== 'woocommerce_payment_complete' ) { return $payload; } $order_id = $payload[0] ?? null; $order = wc_get_order( $order_id ); if ( ! $order ) return $payload; return [ 'order_id' => $order_id, 'status' => $order->get_status(), 'total' => $order->get_total(), 'currency' => $order->get_currency(), 'billing_email' => $order->get_billing_email(), 'line_items' => array_map( fn( $item ) => [ 'product_id' => $item->get_product_id(), 'name' => $item->get_name(), 'quantity' => $item->get_quantity(), ], $order->get_items() ), ]; }, 10, 3 );
/ Conditional Dispatch
How do you filter WooCommerce webhooks by status or product?
Webhook Actions has a built-in condition builder in the admin UI — no PHP required. On the free plan you can attach one condition per webhook (e.g. trigger only when a specific post meta value matches). The Pro plan removes that limit, allowing multiple conditions per webhook combined with AND/OR logic.
For conditions that go beyond what the UI supports — checking order totals, product IDs, or any WooCommerce object property — use the fswa_should_dispatch filter. It fires before a job is queued, so returning false keeps the queue clean rather than delivering and discarding on the receiving end.
PHP — skip webhooks for virtual / free orders
add_filter( 'fswa_should_dispatch', function( $should, $webhook_id, $trigger, $payload ) { if ( $trigger !== 'woocommerce_payment_complete' ) { return $should; } $order_id = $payload[0] ?? null; $order = wc_get_order( $order_id ); // Skip free orders (total = 0) if ( $order && (float) $order->get_total() === 0.0 ) { return false; } return $should; }, 10, 4 );
/ Retry & Replay
What retry behavior applies to failed WooCommerce deliveries?
When a delivery returns a 5xx status or a network error, Webhook Actions marks the attempt failed and schedules a retry via Action Scheduler with exponential backoff: 1 minute, 2 minutes, 4 minutes, 8 minutes — delays capping at 1 hour between attempts. After 5 attempts (configurable via the fswa_max_attempts filter), the delivery is marked permanently_failed.
4xx responses (malformed payload, auth failure) are marked permanently_failed immediately — no retry, because the problem is structural rather than transient.
The delivery log in wp-admin shows every attempt with its HTTP status code and response body. Failed deliveries can be replayed individually or in bulk from the log view — useful when a receiving system undergoes maintenance and you need to re-send a window of missed WooCommerce events without re-triggering the WooCommerce hooks. See the retry and replay architecture for the full backoff schedule and bulk replay workflow.
/ Comparison
WooCommerce built-in webhooks vs Webhook Actions: key differences
| Feature | WooCommerce Built-in Webhooks | Webhook Actions + Action Scheduler |
|---|---|---|
| Delivery timing | Async via Action Scheduler — but delivery schedule and retry window are managed by WooCommerce core, not configurable | Async via Action Scheduler — configurable retry schedule, max attempts, and job grouping |
| Retry logic | Repeated failures → webhook auto-disabled by WooCommerce | 5 attempts, exponential backoff, never auto-disabled |
| Delivery log | Basic — logs exist but limited history | Full per-attempt log with status codes and response body |
| Manual replay | No replay — must re-trigger the WooCommerce event | Replay individual or bulk deliveries from admin log |
| Payload control | Fixed WooCommerce payload format | fswa_webhook_payload filter — any shape, any enrichment |
| Conditional dispatch | No — all matching events are delivered | UI condition builder (free: 1 condition, Pro: unlimited) + fswa_should_dispatch filter for advanced logic |
| Concurrency | No batching — each delivery is independent | Queue runner with configurable concurrent batches |