diff --git a/loader/include/Geode/utils/web.hpp b/loader/include/Geode/utils/web.hpp
index 2dee4074..de85e720 100644
--- a/loader/include/Geode/utils/web.hpp
+++ b/loader/include/Geode/utils/web.hpp
@@ -126,7 +126,6 @@ namespace geode::utils::web {
 
         WebRequest& header(std::string_view name, std::string_view value);
         WebRequest& param(std::string_view name, std::string_view value);
-
         template <std::integral T>
         WebRequest& param(std::string_view name, T value) {
             return this->param(name, std::to_string(value));
@@ -251,5 +250,55 @@ namespace geode::utils::web {
          * @return WebRequest&
          */
         WebRequest& bodyJSON(matjson::Value const& json);
+
+
+        /**
+         * Gets the request method as a string
+         *
+         * @return std::string
+         */
+        std::string getMethod() const;
+
+        /**
+         * Gets the request URL
+         *
+         * @return std::string
+         */
+        std::string getUrl() const;
+
+        /**
+         * Gets the request headers
+         *
+         * @return std::unordered_map<std::string, std::string>
+         */
+        std::unordered_map<std::string, std::string> getHeaders() const;
+
+        /**
+         * Gets the parameters inside the URL
+         *
+         * @return std::unordered_map<std::string, std::string>
+         */
+        std::unordered_map<std::string, std::string> getUrlParams() const;
+
+        /**
+         * Gets the post body stream
+         *
+         * @return std::optional<ByteVector>
+         */
+        std::optional<ByteVector> getBody() const;
+
+        /**
+         * Gets the request timeout in seconds
+         *
+         * @return std::optional<std::chrono::seconds>
+         */
+        std::optional<std::chrono::seconds> getTimeout() const;
+
+        /**
+         * Gets HTTP versions applied to the request
+         *
+         * @return HttpVersion
+         */
+        HttpVersion getHttpVersion() const;
     };
 }
diff --git a/loader/src/utils/web.cpp b/loader/src/utils/web.cpp
index 9c989184..66405463 100644
--- a/loader/src/utils/web.cpp
+++ b/loader/src/utils/web.cpp
@@ -279,9 +279,9 @@ WebTask WebRequest::send(std::string_view method, std::string_view url) {
 
         // Add parameters to the URL and pass it to curl
         auto url = impl->m_url;
-        bool first = true;
-        for (auto param : impl->m_urlParameters) {
-            url += (first ? "?" : "&") + urlParamEncode(param.first) + "=" + urlParamEncode(param.second);
+        bool first = url.find('?') == std::string::npos;
+        for (auto& [key, value] : impl->m_urlParameters) {
+            url += (first ? "?" : "&") + urlParamEncode(key) + "=" + urlParamEncode(value);
             first = false;
         }
         curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
@@ -306,6 +306,7 @@ WebTask WebRequest::send(std::string_view method, std::string_view url) {
         } else if (impl->m_method == "POST") {
             // curl_easy_perform would freeze on a POST request with no fields, so set it to an empty string
             // why? god knows
+            // SMJS: because the stream isn't complete without a body according to the spec
             curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
         }
 
@@ -470,13 +471,39 @@ WebTask WebRequest::patch(std::string_view url) {
 }
 
 WebRequest& WebRequest::header(std::string_view name, std::string_view value) {
+    if (name == "User-Agent") {
+        userAgent(value);
+
+        return *this;
+    } if (name == "Accept-Encoding") {
+        acceptEncoding(value);
+
+        return *this;
+    } if (name == "Keep-Alive") {
+        const size_t timeoutPos = value.find("timeout");
+
+        if (timeoutPos != std::string::npos) {
+            // At this point idc what happens if I get NPOS or string ends, you shouldn't custom format a spec header
+            const size_t numStart = value.find('=', timeoutPos) + 1;
+            const size_t comma = value.find(',', numStart);
+            const size_t numLength = (comma == std::string::npos ? value.size() : comma) - numStart;
+
+            timeout(std::chrono::seconds(std::stol(std::string(value.substr(numStart, numLength)))));
+
+            return *this;
+        }
+    }
+
     m_impl->m_headers.insert_or_assign(std::string(name), std::string(value));
+
     return *this;
 }
+
 WebRequest& WebRequest::param(std::string_view name, std::string_view value) {
     m_impl->m_urlParameters.insert_or_assign(std::string(name), std::string(value));
     return *this;
 }
+
 WebRequest& WebRequest::userAgent(std::string_view name) {
     m_impl->m_userAgent = name;
     return *this;
@@ -540,3 +567,31 @@ WebRequest& WebRequest::bodyJSON(matjson::Value const& json) {
     m_impl->m_body = ByteVector { str.begin(), str.end() };
     return *this;
 }
+
+std::string WebRequest::getMethod() const {
+    return m_impl->m_method;
+}
+
+std::string WebRequest::getUrl() const {
+    return m_impl->m_url;
+}
+
+std::unordered_map<std::string, std::string> WebRequest::getHeaders() const {
+    return m_impl->m_headers;
+}
+
+std::unordered_map<std::string, std::string> WebRequest::getUrlParams() const {
+    return m_impl->m_urlParameters;
+}
+
+std::optional<ByteVector> WebRequest::getBody() const {
+    return m_impl->m_body;
+}
+
+std::optional<std::chrono::seconds> WebRequest::getTimeout() const {
+    return m_impl->m_timeout;
+}
+
+HttpVersion WebRequest::getHttpVersion() const {
+    return m_impl->m_httpVersion;
+}
\ No newline at end of file