TL;DR
- Action Scheduler exposes four scheduling functions: single_action, recurring_action, cron_action, and enqueue_async_action
- All functions return an integer action ID; pass
$unique = trueto skip duplicates automatically as_enqueue_async_actionruns in the next queue cycle with no timestamp;as_schedule_single_actionfires at a specific Unix time- The Webhook Actions plugin uses Action Scheduler under the hood — no manual API calls needed for webhook delivery
/ Overview
What is the Action Scheduler PHP API?
Action Scheduler is a scalable, auditable background-job library for WordPress, maintained by WooCommerce and documented at actionscheduler.org. It ships inside WooCommerce and is available as a standalone plugin. Its PHP API is a set of global functions that let you enqueue, schedule, and cancel background actions without touching WordPress's built-in WP-Cron machinery.
Every scheduled action maps to a WordPress hook. When the action fires, Action Scheduler calls do_action( $hook, ...$args ), so the callback is a regular hook handler. The queue runner — a separate process that pulls and executes pending actions — is the part that makes delivery reliable: it runs outside the page-load cycle, retries failed actions, and keeps a full attempt log.
The API has four scheduling functions, two query functions, and two cancellation functions. All are documented in the official usage guide.
/ Single Action
How does as_schedule_single_action work?
as_schedule_single_action schedules a one-time action to run at a specific Unix timestamp. It is the Action Scheduler equivalent of wp_schedule_single_event — but backed by a persistent database queue instead of the cron option.
PHP — as_schedule_single_action signature
// Schedule a one-time action at $timestamp as_schedule_single_action( int $timestamp, // Unix timestamp — when to run string $hook, // WordPress action hook to fire array $args = [], // Arguments passed to do_action( $hook, ...$args ) string $group = '', // Group name for batching / filtering bool $unique = false, // Skip if identical action already pending int $priority = 10 // Queue priority — lower fires first ): int; // Returns the action ID (or existing ID when $unique=true) // Real-world usage: send webhook 5 seconds from now $action_id = as_schedule_single_action( time() + 5, 'my_plugin_send_webhook', [ 'order_id' => $order_id, 'endpoint' => $url ], 'webhooks', true // skip duplicate if same order_id already queued );
The return value is the integer action ID stored in the wp_actionscheduler_actions table. When $unique is true and an identical pending action exists, the existing action's ID is returned without creating a duplicate. This is the primary idempotency mechanism in the Action Scheduler API.
/ Recurring
How does as_schedule_recurring_action differ?
as_schedule_recurring_action schedules an action to fire repeatedly at a fixed interval in seconds. After each execution, Action Scheduler automatically re-enqueues the next occurrence at $timestamp + N * $interval. Unlike WP-Cron's wp_schedule_event, missed runs do not stack — Action Scheduler always schedules the next slot after the previous one completes.
PHP — as_schedule_recurring_action signature
as_schedule_recurring_action( int $timestamp, // Timestamp of first run int $interval_in_seconds, // Seconds between runs (e.g. 3600 = hourly) string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int; // Register on activation — guard with as_has_scheduled_action if ( ! as_has_scheduled_action( 'my_plugin_hourly_sync' ) ) { as_schedule_recurring_action( time(), 3600, 'my_plugin_hourly_sync', [], 'sync' ); }
/ Async
What does as_enqueue_async_action do?
as_enqueue_async_action queues an action to run as soon as the next queue-processing cycle picks it up — no future timestamp required. It is the right choice when you want work to happen in the background immediately, without the overhead of computing a timestamp or managing intervals.
The function has the same signature as the scheduling functions but without $timestamp. The action is stored with a status of pending and the queue runner claims it in its next batch.
PHP — as_enqueue_async_action
// Run ASAP — next queue cycle, outside the current request $action_id = as_enqueue_async_action( 'my_plugin_process_import', [ 'file' => $upload_path ], 'imports', true // unique — skip if already queued for this file ); // The current HTTP response can now return immediately. // add_action() handler fires later, in the queue runner process. add_action( 'my_plugin_process_import', function( $file ) { // heavy work here — runs outside the user's request } );
/ Cron Action
How does as_schedule_cron_action schedule on a cron expression?
as_schedule_cron_action schedules an action on a cron schedule expression (e.g. 0 6 * * * for 6 AM daily). Unlike as_schedule_recurring_action, which fires at a fixed interval from the previous run, a cron action fires at wall-clock times — it will always run at 6 AM regardless of when the previous run finished. Use it when clock-alignment matters (nightly reports, hourly syncs at the top of the hour).
PHP — as_schedule_cron_action
as_schedule_cron_action( time(), // find next occurrence after this timestamp '0 6 * * *', // standard cron expression: 06:00 UTC daily 'my_plugin_morning_report', [], 'reports' );
/ Query & Cancel
How do you query and cancel scheduled actions?
Two query functions let you inspect the queue before scheduling. as_has_scheduled_action( $hook, $args, $group ) returns true if any matching action is pending or running — use it to avoid duplicate scheduling. as_next_scheduled_action( $hook, $args, $group ) returns the Unix timestamp of the next run, or false if nothing is scheduled.
To remove actions: as_unschedule_action cancels the next occurrence; as_unschedule_all_actions cancels every matching action across all future occurrences. Both accept the same ($hook, $args, $group) signature and are safe to call even if no action is scheduled.
PHP — Query and cancel
// Guard before scheduling if ( as_has_scheduled_action( 'my_plugin_sync', [], 'sync' ) ) { return; // already queued } // Next run timestamp (false if nothing scheduled) $next = as_next_scheduled_action( 'my_plugin_sync' ); // Cancel next occurrence only as_unschedule_action( 'my_plugin_sync', [], 'sync' ); // Cancel all occurrences (use on plugin deactivation) as_unschedule_all_actions( 'my_plugin_sync' );
/ Deduplication
How does the $unique parameter prevent duplicate actions?
When $unique = true, Action Scheduler checks whether an identical action — same $hook, $args, and $group — already exists in a pending or running state before inserting. If one exists, the scheduling call returns the existing action's ID without creating a duplicate.
This is critical for webhook dispatch: if an order is saved multiple times in quick succession (common during WooCommerce checkout), you only want one delivery queued. Without $unique = true, every save fires a new action and the receiving endpoint gets duplicate payloads. The deduplication check compares the serialized $args array, so different argument values always create separate actions even with $unique = true.
Alternatively, the Webhook Actions plugin handles deduplication automatically via its built-in queue: each bound hook fires at most one queued delivery per trigger per webhook, without requiring manual $unique management in application code.
/ Comparison
Raw WP-Cron vs Action Scheduler: what is the difference?
| Feature | Raw WP-Cron (wp_schedule_*) | Action Scheduler (as_schedule_*) |
|---|---|---|
| Storage | wp_options row — no per-action record | Dedicated DB tables — one row per action |
| Delivery log | None — no attempt history | Full attempt log with timestamps and error text |
| Retry | No retry — one attempt only | Automatic retry with configurable attempts |
| Concurrency | No concurrency control — all jobs share one process | Configurable concurrent batches via filter |
| Claim locking | No — duplicate processing possible under load | DB-level claim lock prevents duplicate execution |
| Deduplication | None — manual workaround required | $unique parameter checks pending/running queue |
| Admin UI | None in core | Status, logs, and manual cancel in WP admin |
/ Webhook Integration
How do Webhook Actions and Action Scheduler work together?
The Webhook Actions plugin auto-detects Action Scheduler and uses it as the queue runner when available. When Action Scheduler is present, every triggered webhook delivery is enqueued via as_enqueue_async_action and executed outside the current PHP request. When Action Scheduler is absent, the plugin falls back to WP-Cron automatically.
From a developer perspective you do not call the AS API directly. You bind a WordPress hook (e.g. woocommerce_order_status_changed) to a webhook endpoint in the plugin's admin UI, and the plugin handles the rest: payload serialisation, queuing, header injection (X-Event-Id, X-Event-Timestamp, X-Webhook-Id), exponential-backoff retry (1 min → 2 min → 4 min → 8 min, capped at 1 hour, 5 attempts default), and delivery logging. The fswa_max_attempts and fswa_backoff_delay filters let you tune the retry schedule.
See the Action Scheduler overview for the queue-runner architecture and the concurrent batches guide for tuning under high load.