0) { ob_end_clean(); // or ob_end_flush() — clean is safer here } // Start fresh ob_implicit_flush(true); ob_end_flush(); // extra insurance // Headers MUST come before ANY output header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('X-Accel-Buffering: no'); // LiteSpeed/Apache proxy hint header('Content-Encoding: none'); // prevent compression header('Connection: keep-alive'); // Increase limits (shared hosting may ignore some) @ini_set('output_buffering', 'Off'); @ini_set('zlib.output_compression', 'Off'); @ini_set('implicit_flush', 'On'); set_time_limit(0); ignore_user_abort(true); require_once '../config/db.config.php'; // Quick test ping right away echo ": test comment\n\n"; // SSE comment line — should appear immediately in network tab flush(); $last_id = isset($_SERVER['HTTP_LAST_EVENT_ID']) ? (int)$_SERVER['HTTP_LAST_EVENT_ID'] : 0; echo "event: connected\ndata: SSE stream started at " . date('H:i:s') . " (last_id=$last_id)\n\n"; flush(); while (true) { // Your query here (keep as-is, but use try-catch if not already) try { $stmt = $pdo->prepare(" SELECT a.id, a.scanned_code, ad.username, ad.role, a.scanned_at FROM attendance a INNER JOIN admins ad ON a.scanned_by = ad.id WHERE a.id > ? ORDER BY a.id DESC LIMIT 1 "); $stmt->execute([$last_id]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row) { $last_id = (int)$row['id']; $data = json_encode([ 'id' => $row['id'], 'scanned_code' => htmlspecialchars($row['scanned_code']), 'username' => htmlspecialchars($row['username']), 'role' => $row['role'], 'scanned_at' => date('d M Y, h:i A', strtotime($row['scanned_at'])) ]); echo "id: $last_id\nevent: new_attendance\ndata: $data\n\n"; flush(); } } catch (Exception $e) { echo "event: error\ndata: " . htmlspecialchars($e->getMessage()) . "\n\n"; flush(); } // Heartbeat every ~15s if no data (prevents timeout on some proxies) echo ": heartbeat " . time() . "\n\n"; flush(); sleep(2); }