get_error_message())); } // Display only in this authenticated response. Never persist plaintext credentials. nocache_headers(); header('Referrer-Policy: no-referrer'); wp_enqueue_style('zeopilot-admin', plugins_url('assets/admin.css', __FILE__), array(), ZEOPILOT_CONNECTOR_VERSION); require_once ABSPATH . 'wp-admin/admin-header.php'; zeopilot_connector_render_settings($credential); require_once ABSPATH . 'wp-admin/admin-footer.php'; exit; }); add_action('admin_post_zeopilot_disconnect', function () { if (!current_user_can('manage_options')) { wp_die('You are not allowed to manage this connection.'); } check_admin_referer('zeopilot_disconnect'); if (!zeopilot_connector_revoke()) { wp_die('The connection could not be revoked. Please try again.'); } wp_safe_redirect(add_query_arg( array('zeopilot_disconnected' => '1', 'zeopilot_notice_nonce' => wp_create_nonce('zeopilot_disconnected_notice')), admin_url('options-general.php?page=website-autopilot') )); exit; }); add_action('rest_api_init', function () { register_rest_route('website-autopilot/v1', '/status', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => function () { return zeopilot_connector_api_permission(); }, 'callback' => function () { $state = zeopilot_connector_state(); return rest_ensure_response(array( 'ok' => true, 'plugin_version' => ZEOPILOT_CONNECTOR_VERSION, 'site_url' => home_url('/'), 'wordpress_version' => get_bloginfo('version'), 'permission_profile' => 'page_content_only', 'connection_status' => empty($state['status']) ? 'not_connected' : $state['status'], 'capabilities' => array( 'read_site' => true, 'test_journeys' => true, 'prepare_changes' => true, 'split_testing' => true, 'publish_requires_human_approval' => true, 'rollback_requires_governed_authority' => true, 'normal_password_access' => false, ), )); }, )); }); function zeopilot_connector_permission_row($label, $value, $detail) { $class = $value === 'Never' ? 'zeopilot-never' : ($value === 'Human approval' ? 'zeopilot-approval' : 'zeopilot-allowed'); echo '
'; echo '
' . esc_html($label) . '' . esc_html($detail) . '
'; echo '' . esc_html($value) . ''; echo '
'; } function zeopilot_connector_render_settings($credential = null) { if (!current_user_can('manage_options')) { return; } $user = wp_get_current_user(); $state = zeopilot_connector_state(); ?>

Zeopilot

Connect safely. Stay in control.

Zeopilot uses a dedicated content account with a revocable credential. Your normal WordPress password is never requested.

Zeopilot access was revoked.

The older administrator-level connection was revoked. Create a new content-only connection and update Zeopilot with the new credentials.

One-time connection credential

Copy this into Zeopilot now.

This application password is shown only in this response. WordPress stores only a hashed form.

Connection authority

Exactly what Zeopilot can do

Connection

Create a dedicated connection

WordPress will create a separate account limited to page content and experiment data, with a revocable application password. It cannot manage plugins, themes, users, or site settings.

Zeopilot connection is available

Disconnecting revokes the dedicated application credential. Zeopilot will no longer be able to authenticate to this WordPress site.

$experiment) { if (empty($experiment['active']) || ($experiment['measurement_version'] ?? 0) !== 2) { continue; } if ((int) ($experiment['page_id'] ?? 0) !== (int) $page_id) { continue; } if (!empty($experiment['expires_at']) && strtotime((string) $experiment['expires_at']) <= $now) { continue; } $experiment['id'] = (string) $id; return $experiment; } return null; } function zeopilot_visitor_id() { $name = 'zeopilot_vid'; $cookie = isset($_COOKIE[$name]) && is_string($_COOKIE[$name]) ? sanitize_text_field(wp_unslash($_COOKIE[$name])) : ''; if (preg_match('/^[A-Za-z0-9_-]{16,128}$/', $cookie)) { return $cookie; } $legacy = isset($_COOKIE['wa_vid']) && is_string($_COOKIE['wa_vid']) ? sanitize_text_field(wp_unslash($_COOKIE['wa_vid'])) : ''; $value = preg_match('/^[A-Za-z0-9_-]{16,128}$/', $legacy) ? $legacy : wp_generate_uuid4(); if (!headers_sent()) { setcookie( $name, $value, array( 'expires' => time() + YEAR_IN_SECONDS, 'path' => '/', 'secure' => is_ssl(), 'httponly' => false, 'samesite' => 'Lax', ) ); } $_COOKIE[$name] = $value; return $value; } function zeopilot_experiment_variant($experiment) { $id = (string) ($experiment['id'] ?? ''); $secret = (string) ($experiment['beacon_secret'] ?? ''); // Independent Verifier probes may force the candidate only with a // short-lived HMAC proof. Ordinary synthetic/bot traffic always sees control. if ( $secret !== '' && !empty($_SERVER['HTTP_X_WEBSITE_AUTOPILOT_VARIANT']) ) { $raw = sanitize_text_field( wp_unslash($_SERVER['HTTP_X_WEBSITE_AUTOPILOT_VARIANT']) ); $parts = explode('|', $raw); if (count($parts) === 3 && $parts[0] === 'candidate') { $expires = (int) $parts[1]; $token = strtolower((string) $parts[2]); $now = time(); if ( $expires >= $now && $expires <= ($now + 600) && preg_match('/^[a-f0-9]{64}$/', $token) ) { $expected = hash_hmac( 'sha256', $id . '|candidate|' . $expires, $secret ); if (hash_equals($expected, $token)) { $GLOBALS['zeopilot_experiment_probe'] = true; return 'candidate'; } } } } if (zeopilot_is_synthetic_or_bot()) { return 'control'; } // Old arm cookies are deliberately ignored. Recompute the exact receiver arm. return zeopilot_v2_variant($id, zeopilot_visitor_id(), $experiment['allocation_percent'], $experiment['assignment_secret']); } function zeopilot_current_experiment_context() { if (!is_page()) { return null; } $page_id = get_queried_object_id(); if (!$page_id) { return null; } $experiment = zeopilot_active_experiment_for_page($page_id); if (!$experiment) { return null; } $experiment['variant'] = zeopilot_experiment_variant($experiment); $experiment['visitor_id'] = zeopilot_visitor_id(); return $experiment; } function zeopilot_v2_event($context, $event_type, $metric, $source, $receipt = '') { $event = array('experimentId' => $context['id'], 'visitorId' => $context['visitor_id'], 'variant' => $context['variant'], 'eventType' => $event_type, 'metric' => $metric, 'value' => 1, 'source' => $source, 'occurredAt' => (int) floor(microtime(true) * 1000), 'receiptId' => $receipt, 'expires' => time() + 300); $event['token'] = zeopilot_v2_token($event, $context['beacon_secret']); return $event; } function zeopilot_forward_experiment_event($experiment, $event) { if (($experiment['event_endpoint'] ?? '') !== 'https://zeopilot.com/api/experiments/event') return false; $response = wp_remote_post($experiment['event_endpoint'], array('timeout' => 10, 'redirection' => 0, 'headers' => array('Content-Type' => 'application/json'), 'body' => wp_json_encode($event))); if (is_wp_error($response)) return false; $body = json_decode(wp_remote_retrieve_body($response), true); return wp_remote_retrieve_response_code($response) === 200 && !empty($body['ok']); } // Integrators call this only AFTER their provider durably accepts the outcome. // No browser conversion token is issued for a completed lead/booking/checkout. function zeopilot_confirm_experiment_conversion($id, $visitor_id, $metric, $receipt_id) { $experiments = zeopilot_experiments(); $experiment = $experiments[$id] ?? null; if (!$experiment || empty($experiment['active']) || ($experiment['measurement_version'] ?? 0) !== 2 || strtotime($experiment['expires_at']) <= time() || $metric !== $experiment['primary_metric'] || zeopilot_v2_source($metric) !== 'provider' || !preg_match('/^[A-Za-z0-9_-]{16,128}$/', $visitor_id) || !preg_match('/^[A-Za-z0-9_-]{1,128}$/', $receipt_id)) return false; $experiment['id'] = $id; $experiment['visitor_id'] = $visitor_id; $experiment['variant'] = zeopilot_v2_variant($id, $visitor_id, $experiment['allocation_percent'], $experiment['assignment_secret']); $event = zeopilot_v2_event($experiment, 'conversion', $metric, 'provider', $receipt_id); // Persist a bounded retry before delivery. A provider timeout is not success. $key = 'zeopilot_receipt_' . hash('sha256', $id) . '_' . hash('sha256', $visitor_id . ':' . $metric . ':' . $receipt_id); if (!add_option($key, array('event' => $event, 'attempts' => 0), '', false)) return true; wp_schedule_single_event(time() + 10, 'zeopilot_retry_confirmed_conversion', array($key)); zeopilot_retry_confirmed_conversion($key); return true; } add_action('website_autopilot_conversion_confirmed', 'zeopilot_confirm_experiment_conversion', 10, 4); function zeopilot_retry_confirmed_conversion($key) { $pending = get_option($key); if (!$pending) return; $event = $pending['event']; $experiment = zeopilot_experiments()[$event['experimentId']] ?? null; if (!$experiment || empty($experiment['active']) || strtotime($experiment['expires_at']) <= time()) { if ($experiment) update_option('zeopilot_delivery_failure_' . hash('sha256', $event['experimentId']), array('at' => gmdate('c')), false); delete_option($key); return; } $event['expires'] = time() + 300; $event['token'] = zeopilot_v2_token($event, $experiment['beacon_secret']); if (zeopilot_forward_experiment_event($experiment, $event)) { delete_option($key); return; } $pending['attempts']++; if ($pending['attempts'] >= 5) { // A bounded, non-PII diagnostic. Never label delivery failure as conversion success. update_option('zeopilot_delivery_failure_' . hash('sha256', $event['experimentId']), array('at' => gmdate('c')), false); delete_option($key); return; } update_option($key, $pending, false); wp_schedule_single_event(time() + 30 * $pending['attempts'], 'zeopilot_retry_confirmed_conversion', array($key)); } add_action('zeopilot_retry_confirmed_conversion', 'zeopilot_retry_confirmed_conversion'); add_filter('the_title', function ($title, $post_id = 0) { $context = zeopilot_current_experiment_context(); if (!$context || $context['variant'] !== 'candidate') { return $title; } if ((int) $post_id !== (int) ($context['page_id'] ?? 0)) { return $title; } if (!isset($context['candidate']['title']) || !is_string($context['candidate']['title'])) { return $title; } return $context['candidate']['title']; }, 20, 2); add_filter('the_content', function ($content) { $context = zeopilot_current_experiment_context(); if (!$context || !in_the_loop() || !is_main_query()) return $content; if ($context['variant'] === 'candidate' && isset($context['candidate']['content']) && is_string($context['candidate']['content'])) { $content = wp_kses_post($context['candidate']['content']); } if (zeopilot_is_synthetic_or_bot() || !empty($GLOBALS['zeopilot_experiment_probe'])) return $content; $GLOBALS['zeopilot_rendered_experiment'] = $context; return '
' . $content . '
'; }, 20); add_filter('get_the_excerpt', function ($excerpt, $post = null) { $context = zeopilot_current_experiment_context(); if (!$context || $context['variant'] !== 'candidate') { return $excerpt; } if (!isset($context['candidate']['excerpt']) || !is_string($context['candidate']['excerpt'])) { return $excerpt; } return wp_strip_all_tags($context['candidate']['excerpt']); }, 20, 2); add_action('wp_footer', function () { $context = $GLOBALS['zeopilot_rendered_experiment'] ?? null; if (!$context || zeopilot_is_synthetic_or_bot() || !empty($GLOBALS['zeopilot_experiment_probe'])) return; $metric = $context['primary_metric']; $payload = array( 'endpoint' => rest_url('website-autopilot/v1/experiment-events'), 'experimentId' => $context['id'], 'visitorId' => $context['visitor_id'], 'selector' => $context['conversion_selector'], 'metric' => $metric, 'exposure' => zeopilot_v2_event($context, 'exposure', '', 'browser'), 'click' => zeopilot_v2_source($metric) === 'browser' ? zeopilot_v2_event($context, 'conversion', $metric, 'browser') : null, ); wp_enqueue_script('zeopilot-experiment', plugins_url('assets/experiment.js', __FILE__), array(), ZEOPILOT_CONNECTOR_VERSION, true); wp_add_inline_script('zeopilot-experiment', 'window.zeopilotExperiment = ' . wp_json_encode($payload, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ';', 'before'); }, 15); add_action('rest_api_init', function () { register_rest_route('website-autopilot/v1', '/experiment-events', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => '__return_true', 'callback' => function (WP_REST_Request $request) { $event = $request->get_json_params(); $experiment = zeopilot_experiments()[$event['experimentId'] ?? ''] ?? null; if (!$experiment || ($experiment['measurement_version'] ?? 0) !== 2 || empty($experiment['active']) || strtotime($experiment['expires_at']) <= time() || ($event['source'] ?? '') !== 'browser' || !in_array($event['eventType'] ?? '', array('exposure', 'conversion'), true) || !isset($event['variant'], $event['metric'], $event['occurredAt'], $event['receiptId'], $event['expires'], $event['token']) || ($event['value'] ?? 0) !== 1 || $event['receiptId'] !== '' || !preg_match('/^[A-Za-z0-9_-]{16,128}$/', $event['visitorId'] ?? '') || $event['expires'] < time() || $event['expires'] > time() + 600 || !hash_equals(zeopilot_v2_token($event, $experiment['beacon_secret']), (string) $event['token']) || ($event['eventType'] === 'conversion' && (zeopilot_v2_source($experiment['primary_metric']) !== 'browser' || $event['metric'] !== $experiment['primary_metric']))) { return new WP_Error('invalid_experiment_event', 'Invalid or expired rendered evidence.', array('status' => 400)); } // The render token authorizes this identity, not a pre-render occurrence time. // Seal the time the visible/click signal reaches the same-origin relay. $event['occurredAt'] = (int) floor(microtime(true) * 1000); $event['token'] = zeopilot_v2_token($event, $experiment['beacon_secret']); return new WP_REST_Response(array('ok' => zeopilot_forward_experiment_event($experiment, $event))); }, )); register_rest_route('website-autopilot/v1', '/experiments', array( array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => function () { return zeopilot_connector_api_permission(); }, 'callback' => function () { return rest_ensure_response(array( 'ok' => true, 'measurement_version' => 2, 'confirmed_metrics' => array_values(apply_filters('website_autopilot_confirmed_conversion_metrics', array())), 'browser_metrics' => array('phone_click', 'cta_clicked'), 'cache_safe' => (bool) apply_filters('website_autopilot_experiment_cache_safe', false), )); }, ), array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => function () { return zeopilot_connector_api_permission(); }, 'callback' => function (WP_REST_Request $request) { $data = $request->get_json_params(); $id = isset($data['experiment_id']) ? sanitize_key((string) $data['experiment_id']) : ''; $page_id = isset($data['page_id']) ? absint($data['page_id']) : 0; $allocation = isset($data['allocation_percent']) ? absint($data['allocation_percent']) : 20; $candidate = isset($data['candidate']) && is_array($data['candidate']) ? $data['candidate'] : array(); $event_endpoint = isset($data['event_endpoint']) ? esc_url_raw((string) $data['event_endpoint']) : ''; $beacon_secret = isset($data['beacon_secret']) ? sanitize_text_field((string) $data['beacon_secret']) : ''; $primary_metric = isset($data['primary_metric']) ? sanitize_key((string) $data['primary_metric']) : 'conversion'; $conversion_selector = isset($data['conversion_selector']) ? sanitize_text_field((string) $data['conversion_selector']) : ''; $expires_at = isset($data['expires_at']) ? sanitize_text_field((string) $data['expires_at']) : gmdate('c', time() + (14 * DAY_IN_SECONDS)); $artifact_hash = isset($data['artifact_hash']) ? sanitize_text_field((string) $data['artifact_hash']) : ''; $approval_fingerprint = isset($data['approval_fingerprint']) ? sanitize_text_field((string) $data['approval_fingerprint']) : ''; $assignment_secret = (string) ($data['assignment_secret'] ?? ''); if (($data['measurement_version'] ?? 0) !== 2 || !preg_match('/^[a-f0-9]{64}$/', $assignment_secret)) { return new WP_Error('measurement_upgrade_required', 'Measurement protocol v2 is required.', array('status' => 409)); } if (!apply_filters('website_autopilot_experiment_cache_safe', false)) { return new WP_Error('cache_bypass_required', 'Verify full-page/CDN cache bypass before launching an experiment.', array('status' => 409)); } if (zeopilot_v2_source($primary_metric) === 'provider' && !in_array($primary_metric, apply_filters('website_autopilot_confirmed_conversion_metrics', array()), true)) { return new WP_Error('confirmation_adapter_required', 'Install a confirmed-outcome adapter for this goal before launching.', array('status' => 409)); } if (!is_numeric($data['allocation_percent'] ?? null) || $data['allocation_percent'] < 0 || $data['allocation_percent'] > 100 || !strtotime($expires_at) || strtotime($expires_at) <= time()) { return new WP_Error('invalid_experiment_window', 'A valid allocation and future deadline are required.', array('status' => 400)); } if ($id === '' || !$page_id || get_post_type($page_id) !== 'page' || !current_user_can('edit_post', $page_id)) { return new WP_Error('invalid_experiment', 'A valid experiment id and page id are required.', array('status' => 400)); } if ($event_endpoint !== 'https://zeopilot.com/api/experiments/event') { return new WP_Error('invalid_event_endpoint', 'The Zeopilot HTTPS experiment endpoint is required.', array('status' => 400)); } if (strlen($beacon_secret) < 32) { return new WP_Error('invalid_beacon_secret', 'Experiment beacon secret is invalid.', array('status' => 400)); } if ($artifact_hash === '' || $approval_fingerprint === '') { return new WP_Error('approval_binding_required', 'Artifact and approval identity are required.', array('status' => 400)); } $safe_candidate = array(); foreach (array('title', 'content', 'excerpt') as $field) { if (isset($candidate[$field]) && is_string($candidate[$field])) { $safe_candidate[$field] = $field === 'content' ? wp_kses_post($candidate[$field]) : sanitize_text_field($candidate[$field]); } } $experiments = zeopilot_experiments(); // Never overwrite active allocation, identity or artifacts on a retry. if (isset($experiments[$id])) { $existing = $experiments[$id]; if (($existing['measurement_version'] ?? 0) !== 2 || $existing['artifact_hash'] !== $artifact_hash || $existing['approval_fingerprint'] !== $approval_fingerprint || $existing['allocation_percent'] !== $allocation || $existing['page_id'] !== $page_id || $existing['primary_metric'] !== $primary_metric || $existing['expires_at'] !== $expires_at || $existing['candidate'] !== $safe_candidate || $existing['conversion_selector'] !== $conversion_selector || $existing['event_endpoint'] !== $event_endpoint || $existing['assignment_secret'] !== $assignment_secret || $existing['beacon_secret'] !== $beacon_secret) { return new WP_Error('experiment_identity_conflict', 'Use a new experiment identity for changed configuration.', array('status' => 409)); } } foreach ($experiments as $other_id => $other) { if ($other_id !== $id && !empty($other['active']) && (int) $other['page_id'] === $page_id && strtotime($other['expires_at']) > time()) { return new WP_Error('experiment_page_collision', 'This page already has an active experiment.', array('status' => 409)); } } $experiments[$id] = array( 'measurement_version' => 2, 'assignment_secret' => $assignment_secret, 'active' => true, 'page_id' => $page_id, 'allocation_percent' => $allocation, 'candidate' => $safe_candidate, 'event_endpoint' => $event_endpoint, 'beacon_secret' => $beacon_secret, 'primary_metric' => $primary_metric, 'conversion_selector' => $conversion_selector, 'expires_at' => $expires_at, 'artifact_hash' => $artifact_hash, 'approval_fingerprint' => $approval_fingerprint, 'created_at' => gmdate('c'), ); zeopilot_save_experiments($experiments); return rest_ensure_response(array( 'ok' => true, 'experiment_id' => $id, 'status' => 'running', 'measurement_version' => 2, 'artifact_hash' => $artifact_hash, 'approval_fingerprint' => $approval_fingerprint, )); }, ), )); register_rest_route('website-autopilot/v1', '/experiments/(?P[a-zA-Z0-9_-]+)', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => function () { return zeopilot_connector_api_permission(); }, 'callback' => function (WP_REST_Request $request) { $id = sanitize_key((string) $request['id']); $experiments = zeopilot_experiments(); $row = $experiments[$id] ?? null; $running = $row && !empty($row['active']) && strtotime($row['expires_at'] ?? '') > time(); return rest_ensure_response(array('ok' => true, 'experiment_id' => $id, 'measurement_version' => 2, 'status' => $running ? 'running' : 'stopped', 'artifact_hash' => $row['artifact_hash'] ?? null)); }, )); register_rest_route('website-autopilot/v1', '/experiments/(?P[a-zA-Z0-9_-]+)', array( 'methods' => WP_REST_Server::DELETABLE, 'permission_callback' => function () { return zeopilot_connector_api_permission(); }, 'callback' => function (WP_REST_Request $request) { $id = sanitize_key((string) $request['id']); global $wpdb; // Accurate read-only count of pending hashed receipts. Caching here could // falsely report a completed provider outcome before its final acknowledgement. $pending = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Bounded indexed COUNT; immediate truth is required. "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('zeopilot_receipt_' . hash('sha256', $id) . '_') . '%' )); $experiments = zeopilot_experiments(); if (isset($experiments[$id])) { unset($experiments[$id]); zeopilot_save_experiments($experiments); } return rest_ensure_response(array( 'ok' => true, 'experiment_id' => $id, 'status' => 'stopped', 'measurement_version' => 2, 'delivery_failure' => $pending > 0 || (bool) get_option('zeopilot_delivery_failure_' . hash('sha256', $id), false), )); }, )); }); add_action('template_redirect', function () { if (zeopilot_is_synthetic_or_bot()) { return; } if (zeopilot_current_experiment_context()) { if (!defined('DONOTCACHEPAGE')) define('DONOTCACHEPAGE', true); nocache_headers(); } }, 1);