Monitoring converts “something went wrong” from a customer complaint into an internal alert. An HTTP 200 check tells you the server responded. A synthetic form check tells you the form actually works. Both are required.
A deploy that doesn’t break the homepage can still break the contact form. Standard uptime monitoring — which checks that a URL returns HTTP 200 — doesn’t catch functional failures. Only a check that verifies the form actually processes a submission catches the class of bug that shows up most often: a PHP change that breaks a specific AJAX endpoint while leaving the rest of the site intact.
The Anchor build went live without a monitoring layer. Two weeks in, the AJAX endpoint for the contact form silently returned an error for 3 hours — a PHP change had an edge case nobody caught in testing. The owner found out when a customer called to say the form “didn’t work.” The fix was 4 minutes. The exposure window was 3 hours. After that incident: external uptime monitoring, synthetic form submission checks, and PHP error log alerting.
An external uptime service (UptimeRobot, Better Uptime, or equivalent) sends HTTP requests to key URLs every 5 minutes and alerts on non-200 responses or response time thresholds:
Monitor targets:
- https://[site]/ → 200 within 3s
- https://[site]/inventory/ → 200 within 4s, contains "unit-card"
- https://[site]/wp-admin/admin-ajax.php → 400 (expected for unauthenticated)
- https://[site]/wp-json/ → 200 (REST API up)
Alert channels:
- SMS to owner's phone (P1 — site down)
- Email to owner (P2 — slow response)
- Slack/webhook for dev team (all alerts)
A cron job runs every 15 minutes and submits a test lead through the contact form AJAX endpoint. If the response isn’t a success JSON, it sends an alert:
add_action( '[client]_synthetic_form_check', '[client]_run_synthetic_check' );
function [client]_run_synthetic_check(): void {
$response = wp_remote_post( home_url( '/wp-admin/admin-ajax.php' ), [
'timeout' => 10,
'body' => [
'action' => '[client]_contact_lead',
'first_name' => 'Synthetic',
'last_name' => 'Check',
'email' => 'synthetic@internal.[site]',
'phone' => '5555550000',
'message' => 'Automated synthetic check — not a real lead',
'is_synthetic' => '1',
[client]_get_nonce_field_name() => wp_create_nonce( '[client]_contact' ),
],
] );
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
[client]_send_monitoring_alert( 'Contact form AJAX endpoint failed', [
'status' => wp_remote_retrieve_response_code( $response ),
'body' => wp_remote_retrieve_body( $response ),
] );
return;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $body['success'] ) ) {
[client]_send_monitoring_alert( 'Contact form returned success:false', $body );
}
}
function [client]_send_monitoring_alert( string $message, array $context = [] ): void {
wp_mail(
[client]_get_setting( 'alert_email', get_option( 'admin_email' ) ),
'[ALERT] ' . get_bloginfo( 'name' ) . ': ' . $message,
print_r( $context, true )
);
}
The error log is the first place real bugs appear. A cron job scans the PHP error log every 30 minutes for new Fatal error or PHP Warning entries using incremental file position tracking to avoid re-reading the entire log on every pass:
function [client]_scan_error_log(): void {
$log_path = ini_get( 'error_log' );
if ( ! $log_path || ! file_exists( $log_path ) ) {
return;
}
$last_checked = get_option( '[client]_error_log_last_pos', 0 );
$current_size = filesize( $log_path );
if ( $current_size <= $last_checked ) {
update_option( '[client]_error_log_last_pos', $current_size );
return;
}
$handle = fopen( $log_path, 'r' );
fseek( $handle, $last_checked );
$new_lines = '';
while ( ( $line = fgets( $handle ) ) !== false ) {
if ( str_contains( $line, 'Fatal error' ) || str_contains( $line, 'PHP Warning' ) ) {
$new_lines .= $line;
}
}
fclose( $handle );
update_option( '[client]_error_log_last_pos', $current_size );
if ( $new_lines ) {
[client]_send_monitoring_alert( 'New PHP errors detected', [ 'log_excerpt' => $new_lines ] );
}
}
The contact form check catches the most common production failure mode: a code change that breaks a specific AJAX endpoint while leaving the rest of the site intact. Standard uptime monitoring doesn’t catch this — the site returns 200. Only a check that verifies functional behavior catches it.
The error log scan catches fatal PHP errors before they cascade. A single fatal error in a function called on every page load can silently degrade performance for hundreds of requests before uptime monitoring notices the increased response time. The incremental file position pattern means the scan doesn’t re-read the full log every 30 minutes — it picks up from the last byte read, so the scan stays fast even on high-volume error logs.
External uptime monitoring on 4 endpoints. Synthetic form check running every 15 minutes. PHP error log scan every 30 minutes. P1 MTTD (mean time to detect) after monitoring went live: 4 minutes. Prior: 3 hours (owner found out from a customer call). Zero customer-reported P1 incidents after monitoring was in place. The synthetic check caught 2 regression bugs in the first 6 months — both were PHP changes that had no visible effect on the homepage but broke the contact form AJAX handler.
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 →