TL;DR: as_schedule_recurring_action() runs a hook repeatedly at a fixed interval in seconds, re-enqueuing the next occurrence only after the current one finishes.
- Signature:
as_schedule_recurring_action( int $timestamp, int $interval_in_seconds, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ) $timestampis the first run; each later run is scheduled atprevious_run + $interval, so missed runs do not stack.- Guard registration with
as_has_scheduled_action()or$unique = trueso activation does not create a second recurring series.
/ Signature
What is the as_schedule_recurring_action signature?
as_schedule_recurring_action() schedules a background action to fire again and again at a fixed number of seconds apart. It is part of Action Scheduler, the queue library bundled with WooCommerce. Like every scheduling function, it maps to a WordPress hook: each run calls do_action( $hook, ...$args ), so the executing code is an ordinary add_action() callback.
PHP — as_schedule_recurring_action full signature
as_schedule_recurring_action( int $timestamp, // Unix time of the first run int $interval_in_seconds, // gap between runs, e.g. 3600 = hourly string $hook, // action hook fired via do_action() array $args = [], // args spread into the callback string $group = '', // label for filtering / bulk cancel bool $unique = false, // skip if an identical series is pending int $priority = 10 // 0-255, lower runs first (AS 3.5+) ): int; // returns the first action's ID, or 0 on failure
It mirrors as_schedule_single_action() exactly, with one extra second argument: $interval_in_seconds. Everything after it — $hook, $args, $group, $unique, $priority — behaves identically.
/ Interval
How does the $interval_in_seconds parameter work?
The interval is a plain integer count of seconds — there are no named schedules like WP-Cron's hourly or daily. Compute it directly: HOUR_IN_SECONDS (3600), DAY_IN_SECONDS (86400), or your own value. Crucially, the interval is measured from when each run completes, not from a fixed wall-clock grid. After an occurrence finishes, Action Scheduler enqueues the next one at that run's scheduled time plus the interval, so a job set to run "every hour" drifts slightly if runs are delayed. When you need a job pinned to the clock (always at the top of the hour, always at 06:00), use as_schedule_cron_action() with a cron expression instead.
| Goal | Interval value | WordPress constant |
|---|---|---|
| Every 5 minutes | 300 | 5 * MINUTE_IN_SECONDS |
| Hourly | 3600 | HOUR_IN_SECONDS |
| Every 6 hours | 21600 | 6 * HOUR_IN_SECONDS |
| Daily | 86400 | DAY_IN_SECONDS |
| Weekly | 604800 | WEEK_IN_SECONDS |
/ Missed runs
What happens to missed recurring runs?
They do not pile up. Because the next occurrence is only scheduled after the previous one finishes, a site that goes hours without triggering the queue runs the action once when the queue next fires, then schedules the following occurrence from there. This is the opposite of WP-Cron's wp_schedule_event(), where a backlog of overdue events can all fire in a burst on the next page load. The trade-off is drift: if runs are consistently late, "hourly" becomes "every hour plus the lateness". For most maintenance work that is exactly what you want.
/ Registration
How do you register a recurring action without duplicates?
The classic bug is calling as_schedule_recurring_action() on every plugin load, or on every activation, which creates a second, third, and fourth parallel series — all firing the same hook. Guard the call so it only schedules when nothing is already queued. The two safe patterns are an as_has_scheduled_action() check or the $unique = true flag.
PHP — register once, on init or activation
add_action( 'init', function() { // Only schedule if this series is not already queued if ( as_has_scheduled_action( 'myplugin_hourly_sync', [], 'sync' ) ) { return; } as_schedule_recurring_action( time(), HOUR_IN_SECONDS, 'myplugin_hourly_sync', [], 'sync' ); } ); add_action( 'myplugin_hourly_sync', function() { // recurring background work runs here } );
Recurring actions schedule the next run after the current one finishes — so a stalled site never wakes up to a stampede of overdue jobs. — Action Scheduler admin guide
/ vs recurring WP-Cron
How does it compare to a recurring WP-Cron event?
Both repeat a hook on a schedule, but Action Scheduler adds persistence, per-run logging, and retry — and it will not fire a backlog all at once.
| Feature | wp_schedule_event (recurring) | as_schedule_recurring_action |
|---|---|---|
| Schedule unit | Named schedule (hourly, daily…) | Interval in seconds |
| Missed runs | Can fire in a burst on next load | One run, then reschedule forward |
| Retry | None | Automatic on failure |
| Per-run log | None | Full attempt history in wp-admin |
| Cancel one series | wp_clear_scheduled_hook (all) | as_unschedule_all_actions by hook/group |
/ Cancel
How do you stop a recurring action?
Call as_unschedule_all_actions( $hook, $args, $group ) to cancel the whole series, typically on plugin deactivation. It is safe to call even when nothing is scheduled — it is a no-op rather than an error. To inspect before acting, as_next_scheduled_action( $hook ) returns the next run's Unix timestamp, or false if the series has stopped. The full query-and-cancel set is covered in the Action Scheduler PHP API reference.
/ Webhooks
Where does this fit with webhook delivery?
Recurring actions are for polling and maintenance — syncing a remote list every hour, pruning logs nightly — not for event-driven webhooks. When you want an outbound call the moment something happens in WordPress, you want an event trigger, not a timer: hook do_action and POST on the event itself, behind a queue that retries and logs each delivery. Use as_schedule_recurring_action() when the trigger is genuinely time-based, and an event-driven webhook when it is not. For the whole scheduling family, see the API functions reference.