The push log table, immediate failure alerts, and the daily stale-listing audit that catches silent non-pushes the immediate alert misses.
Marketplace pushes can fail silently. An API can return a success response that contains an error body. A unit can be skipped because validation failed, with no one notified. A cron job that fires the push can be disabled by a caching plugin. Without logging every push attempt and checking the aggregate result on a schedule, failures accumulate invisibly — and listings on the marketplace get stale without anyone knowing.
After the feed architecture was working, the question was: how do you know if it’s still working next Tuesday? The push is async. There’s no user watching it succeed. An API credential that rotates, a marketplace that changes its endpoint, a quota limit hit during a bulk update — any of these produces failures with no visible signal unless there’s a log and something that reads the log.
Every push attempt — success or failure — gets a row in a dedicated log table. The table is created on plugin activation:
CREATE TABLE IF NOT EXISTS [client]_feed_push_log (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
unit_id BIGINT UNSIGNED NOT NULL,
marketplace VARCHAR(50) NOT NULL,
status ENUM('success','failed','validation_error','skipped') NOT NULL,
response_code SMALLINT NOT NULL DEFAULT 0,
response_body TEXT,
pushed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_unit_marketplace (unit_id, marketplace),
KEY idx_pushed_at (pushed_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
function [client]_log_feed_push(
int $unit_id,
string $marketplace,
string $status,
int $response_code,
string $response_body = ''
): void {
global $wpdb;
$wpdb->insert(
'[client]_feed_push_log',
[
'unit_id' => $unit_id,
'marketplace' => $marketplace,
'status' => $status,
'response_code' => $response_code,
'response_body' => substr( $response_body, 0, 2000 ), // cap body size
'pushed_at' => current_time( 'mysql' ),
],
[ '%d', '%s', '%s', '%d', '%s', '%s' ]
);
}
function [client]_push_to_marketplace( int $unit_id, string $marketplace_id, array $record ): void {
$creds = [client]_get_marketplace_credentials( $marketplace_id );
$payload = [client]_map_feed_record( $marketplace_id, $record );
$response = [client]_call_marketplace_api( $marketplace_id, $payload, $creds );
$code = wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
if ( is_wp_error( $response ) || $code < 200 || $code >= 300 ) {
[client]_log_feed_push( $unit_id, $marketplace_id, 'failed', (int) $code, $body );
[client]_alert_on_push_failure( $unit_id, $marketplace_id, $code, $body );
return;
}
[client]_log_feed_push( $unit_id, $marketplace_id, 'success', (int) $code, '' );
}
When a push fails, the admin gets an email immediately with the unit, marketplace, HTTP response code, and the full response body. The response body is what tells you whether it’s a credential issue, a quota issue, or a malformed payload:
function [client]_alert_on_push_failure(
int $unit_id,
string $marketplace,
int $code,
string $body
): void {
$unit_title = get_the_title( $unit_id );
$admin_url = admin_url( 'admin.php?page=[client]_feed_log&unit_id=' . $unit_id );
$subject = "[client] Feed Push Failed: {$unit_title} on {$marketplace}";
$message = "A marketplace push failed.\n\n"
. "Unit: {$unit_title} (ID: {$unit_id})\n"
. "Marketplace: {$marketplace}\n"
. "HTTP Code: {$code}\n\n"
. "Response body:\n{$body}\n\n"
. "View log: {$admin_url}";
wp_mail( get_option( 'admin_email' ), $subject, $message );
}
Immediate alerts catch individual failures. The daily audit catches the cases the immediate alert misses: a cron that was disabled for 48 hours, a unit that was never pushed because its initial push happened during an API outage with no retry, or a marketplace that stopped accepting updates without returning an error.
// Register the daily audit
add_action( 'wp', function() {
if ( ! wp_next_scheduled( '[client]_daily_feed_audit' ) ) {
wp_schedule_event( strtotime( 'tomorrow 07:00' ), 'daily', '[client]_daily_feed_audit' );
}
} );
add_action( '[client]_daily_feed_audit', '[client]_audit_stale_listings' );
function [client]_audit_stale_listings(): void {
global $wpdb;
// Find active units with no successful push in the last 25 hours
$stale = $wpdb->get_results(
"SELECT DISTINCT p.ID, p.post_title, l.marketplace
FROM {$wpdb->posts} p
INNER JOIN [client]_feed_push_log l ON l.unit_id = p.ID
WHERE p.post_type = '[client]_unit'
AND p.post_status = 'publish'
AND l.status != 'success'
AND NOT EXISTS (
SELECT 1 FROM [client]_feed_push_log s
WHERE s.unit_id = p.ID
AND s.marketplace = l.marketplace
AND s.status = 'success'
AND s.pushed_at >= DATE_SUB( NOW(), INTERVAL 25 HOUR )
)
ORDER BY p.post_title"
);
if ( empty( $stale ) ) {
return;
}
$lines = array_map(
fn( $r ) => "- {$r->post_title} (ID: {$r->ID}) on {$r->marketplace}",
$stale
);
$message = "The following units have not pushed successfully in the last 25 hours:\n\n"
. implode( "\n", $lines )
. "\n\nReview the feed log: " . admin_url( 'admin.php?page=[client]_feed_log' );
wp_mail( get_option( 'admin_email' ), '[client] Stale Listings Audit', $message );
}
The immediate alert and the daily audit serve different failure modes. The immediate alert fires on errors that return something — a bad response code, a WP_Error, a non-2xx response. The daily audit fires on absence — a unit that should have pushed but didn’t, because a cron was disabled, because validation silently rejected it, because a push was queued and never executed.
The 25-hour window in the audit accounts for small timing drifts in daily processes while still catching units that were missed for a full day.
The push log answered three real incidents: a credential rotation where the old API key was still in the database for 48 hours after the new one was set, a bulk price update that hit a rate limit on the third marketplace and failed silently on units 51–130, and a caching-plugin update that disabled WP-Cron for 18 hours. In all three cases, the log provided the exact timeline and scope of the failure. Without it, the incidents would have required checking four marketplace dashboards manually to determine what was out of date.
Every lesson stays free — no account, no paywall, no email gate, ever. But if you’d rather have this system standing on your business than wire all 48 lessons yourself, leave your email. We’ll send you a direct line to a build — and you’ll be first to hear when we add new tools to the curriculum.
None of this gates a single lesson. The curriculum was free before you got here and it stays that way.
You came here to understand the system, and now you do. If you’d rather have it standing on your business than spend the next three months wiring it yourself, GAP Concierge is the same architecture from these lessons — a white-label AI agent that knows your catalog and captures your leads — set up for you, from $97/mo.
See GAP Concierge →