Developers

Run your own code when an import completes

Published

SSI fires two WordPress actions when imports finish, so you can bolt on whatever you like: Slack messages, webhooks to a downstream system, cache warming, kicking off a second dependent import. This article covers both hooks, what they pass you, and a worked example you can paste in.

If all you want is an email, you don’t need code at all: the Schedule tab → Email alerts panel has built-in per-import completion emails and a daily morning summary (added in 2.72). The hooks are for everything beyond that.

The two hooks

ssi_import_run_complete: every completed run

Fires exactly once for every run that completes, no matter how it was started: the Preview & Run button in the browser, wp ssi <id> on the CLI, or a schedule. If you want "tell me whenever anything finishes", this is the hook.

do_action( 'ssi_import_run_complete', $import_id, $history_id, $summary );
Parameter Type What it is
$import_id int The import’s id, the number you see in wp ssi list and pass to wp ssi <id>.
$history_id int The id of this run’s row in the wp_ssi_import_history table. Use it to look up the full stage-by-stage record of the run.
$summary array The run’s counters. The keys you’ll actually use: rows_processed, posts_inserted, posts_updated, plus per-stage detail counters. Always check with isset(): a narrowed run (for example the Update prices/stock only mode) skips stages, so it only reports the counters for stages that ran.

Note it fires on completion: a run that dies mid-way (fatal, out of memory, server restart) never reaches it. For "did my import NOT run?" monitoring, use the built-in daily summary email, which explicitly reports when nothing ran.

ssi_scheduled_import_complete: scheduled runs only

Fires when a scheduled run completes, right after SSI has updated the schedule’s bookkeeping (last run, next run). Manual browser runs and CLI runs do not fire this one. Use it when your code cares about the schedule itself: cadence, next run time, whether it’s still enabled.

do_action( 'ssi_scheduled_import_complete', $import_id, $schedule );
Parameter Type What it is
$import_id int The import’s id, same as above.
$schedule array The schedule row. Keys: id, import_id, cadence (once / hourly / daily / weekly), enabled, run_at, run_days, timezone, next_run, last_run, last_result, status, history_id (the history row of the run that just finished).

A scheduled run fires both hooks: ssi_import_run_complete first (when the run’s history row is finalised), then ssi_scheduled_import_complete (when the schedule bookkeeping is done).

Where to put the code

A must-use plugin (mu-plugin) is the right home: it loads on every request including WP-Cron and CLI, it survives theme switches, and it can’t be deactivated by accident. Create the file wp-content/mu-plugins/ssi-completion-hooks.php (make the mu-plugins folder if it doesn’t exist) and it’s live immediately, as mu-plugins don’t need activating.

A theme’s functions.php also works, but remember scheduled runs execute under WP-Cron with your active theme: if you switch themes, your hook silently disappears. That’s the footgun the mu-plugin avoids.

Worked example: notify a webhook when the nightly product feed lands

Say import 7 is your nightly supplier feed, scheduled daily at 03:00, and you want your stock dashboard pinged when it’s done, with the counts, and a warning if the run looks suspiciously small.

<?php
/**
 * Plugin Name: SSI completion notifications
 * Description: Pings the stock dashboard when the nightly feed (import 7) completes.
 */

add_action( 'ssi_scheduled_import_complete', function ( $import_id, $schedule ) {

    // Only care about the nightly feed.
    if ( 7 !== (int) $import_id ) {
        return;
    }

    // Pull the counters for THIS run via the history row the schedule hands us.
    global $wpdb;
    $history = $wpdb->get_row( $wpdb->prepare(
        "SELECT summary_data, duration FROM {$wpdb->prefix}ssi_import_history WHERE id = %d",
        (int) $schedule['history_id']
    ) );
    $summary = $history ? json_decode( $history->summary_data, true ) : array();

    $rows    = isset( $summary['rows_processed'] ) ? (int) $summary['rows_processed'] : 0;
    $created = isset( $summary['posts_inserted'] ) ? (int) $summary['posts_inserted'] : 0;
    $updated = isset( $summary['posts_updated'] ) ? (int) $summary['posts_updated'] : 0;

    wp_remote_post( 'https://dashboard.example.com/hooks/feed-complete', array(
        'timeout' => 10,
        'headers' => array( 'Content-Type' => 'application/json' ),
        'body'    => wp_json_encode( array(
            'import_id'  => $import_id,
            'rows'       => $rows,
            'created'    => $created,
            'updated'    => $updated,
            'duration'   => $history ? round( (float) $history->duration, 1 ) : null,
            'next_run'   => $schedule['next_run'],
            // A normal feed is ~40k rows; flag a tiny one so someone looks at it.
            'suspicious' => $rows < 1000,
        ) ),
    ) );

}, 10, 2 );

Three things worth copying from this example:

  • The 10, 2 at the end matters. add_action defaults to passing your callback ONE argument; without the , 2 you’d never receive $schedule and $schedule['history_id'] would fatal. Same deal with , 3 for ssi_import_run_complete.
  • $schedule['history_id'] is the bridge to the run’s detail. The schedule row itself only carries bookkeeping; join to wp_ssi_import_history for counters, and to wp_ssi_import_history_stages if you want per-stage timings.
  • Sanity-check the counters. A supplier feed that normally moves 40k rows and suddenly moves 12 is a truncated CSV, not a quiet night. Completion is not the same as correctness: cheap threshold checks like $rows < 1000 catch real incidents.

Variant: the same thing for ANY run, not just scheduled

Hooking ssi_import_run_complete is simpler because the counters are handed to you directly, with no history lookup needed:

add_action( 'ssi_import_run_complete', function ( $import_id, $history_id, $summary ) {
    if ( 7 !== (int) $import_id ) {
        return;
    }
    $rows = isset( $summary['rows_processed'] ) ? (int) $summary['rows_processed'] : 0;
    error_log( sprintf( 'SSI import %d finished (history %d): %d rows.', $import_id, $history_id, $rows ) );
}, 10, 3 );

This fires for manual test runs from the Config screen too, which is handy while you’re developing the listener, since you don’t have to wait for the schedule to tick. Run the import from the browser (or wp ssi 7), then check wp-content/debug.log.

Testing your listener

  1. Drop the mu-plugin in place.
  2. Point it at a small test import id and run that import from the Config screen.
  3. For the scheduled hook specifically: set the import’s schedule to once a couple of minutes out, then either wait for WP-Cron or force the due-schedule scan with wp cron event run ssi_scheduled_scan_due (or just load any front-end page to trigger wp-cron).
  4. Check your endpoint or debug.log. If nothing arrived, the usual suspect is the missing argument count (, 2 / , 3).

Comments

▲
▼
1/1