diff --git a/src/common/web_result.h b/src/common/web_result.h
index 9699266740148ee933000696714f91a586e34516..8bfa2141d8be7dc635fce2ca0a5a5e4533d46927 100644
--- a/src/common/web_result.h
+++ b/src/common/web_result.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <string>
+#include "common/common_types.h"
 
 namespace Common {
 struct WebResult {
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 3c5590054b5aa6d0688527922230c794862246ac..0a8f2bd9e12a1e1199705d90dca4be1248696b6b 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -4,6 +4,7 @@
 
 #include <json.hpp>
 #include "common/detached_tasks.h"
+#include "common/web_result.h"
 #include "web_service/telemetry_json.h"
 #include "web_service/web_backend.h"
 
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
index 124aa3863e26017caf8db697e5ce6419f2eb3142..ca4b43b938c70790ac16ab1f845025cb498391ad 100644
--- a/src/web_service/verify_login.cpp
+++ b/src/web_service/verify_login.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <json.hpp>
+#include "common/web_result.h"
 #include "web_service/verify_login.h"
 #include "web_service/web_backend.h"
 
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 787b0fbcbd10d3cccf0f2a25a9c81cc51f3b4245..b7737b6156c0a7bb8d22408cb73c869e1c7eb8fa 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -3,9 +3,11 @@
 // Refer to the license.txt file included.
 
 #include <cstdlib>
+#include <mutex>
 #include <string>
-#include <thread>
 #include <LUrlParser.h>
+#include <httplib.h>
+#include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/web_result.h"
 #include "core/settings.h"
@@ -20,99 +22,132 @@ constexpr u32 HTTPS_PORT = 443;
 
 constexpr u32 TIMEOUT_SECONDS = 30;
 
-Client::JWTCache Client::jwt_cache{};
+struct Client::Impl {
+    Impl(std::string host, std::string username, std::string token)
+        : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
+        std::lock_guard<std::mutex> lock(jwt_cache.mutex);
+        if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
+            jwt = jwt_cache.jwt;
+        }
+    }
+
+    /// A generic function handles POST, GET and DELETE request together
+    Common::WebResult GenericJson(const std::string& method, const std::string& path,
+                                  const std::string& data, bool allow_anonymous) {
+        if (jwt.empty()) {
+            UpdateJWT();
+        }
+
+        if (jwt.empty() && !allow_anonymous) {
+            LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
+            return Common::WebResult{Common::WebResult::Code::CredentialsMissing,
+                                     "Credentials needed"};
+        }
+
+        auto result = GenericJson(method, path, data, jwt);
+        if (result.result_string == "401") {
+            // Try again with new JWT
+            UpdateJWT();
+            result = GenericJson(method, path, data, jwt);
+        }
 
-Client::Client(const std::string& host, const std::string& username, const std::string& token)
-    : host(host), username(username), token(token) {
-    std::lock_guard<std::mutex> lock(jwt_cache.mutex);
-    if (username == jwt_cache.username && token == jwt_cache.token) {
-        jwt = jwt_cache.jwt;
+        return result;
     }
-}
 
-Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
-                                      const std::string& data, const std::string& jwt,
-                                      const std::string& username, const std::string& token) {
-    if (cli == nullptr) {
-        auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
-        int port;
-        if (parsedUrl.m_Scheme == "http") {
-            if (!parsedUrl.GetPort(&port)) {
-                port = HTTP_PORT;
-            }
-            cli =
-                std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
-        } else if (parsedUrl.m_Scheme == "https") {
-            if (!parsedUrl.GetPort(&port)) {
-                port = HTTPS_PORT;
+    /**
+     * A generic function with explicit authentication method specified
+     * JWT is used if the jwt parameter is not empty
+     * username + token is used if jwt is empty but username and token are not empty
+     * anonymous if all of jwt, username and token are empty
+     */
+    Common::WebResult GenericJson(const std::string& method, const std::string& path,
+                                  const std::string& data, const std::string& jwt = "",
+                                  const std::string& username = "", const std::string& token = "") {
+        if (cli == nullptr) {
+            auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
+            int port;
+            if (parsedUrl.m_Scheme == "http") {
+                if (!parsedUrl.GetPort(&port)) {
+                    port = HTTP_PORT;
+                }
+                cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port,
+                                                        TIMEOUT_SECONDS);
+            } else if (parsedUrl.m_Scheme == "https") {
+                if (!parsedUrl.GetPort(&port)) {
+                    port = HTTPS_PORT;
+                }
+                cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
+                                                           TIMEOUT_SECONDS);
+            } else {
+                LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
+                return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
             }
-            cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
-                                                       TIMEOUT_SECONDS);
-        } else {
-            LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
-            return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
         }
-    }
-    if (cli == nullptr) {
-        LOG_ERROR(WebService, "Invalid URL {}", host + path);
-        return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
-    }
+        if (cli == nullptr) {
+            LOG_ERROR(WebService, "Invalid URL {}", host + path);
+            return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
+        }
 
-    httplib::Headers params;
-    if (!jwt.empty()) {
-        params = {
-            {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
-        };
-    } else if (!username.empty()) {
-        params = {
-            {std::string("x-username"), username},
-            {std::string("x-token"), token},
+        httplib::Headers params;
+        if (!jwt.empty()) {
+            params = {
+                {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
+            };
+        } else if (!username.empty()) {
+            params = {
+                {std::string("x-username"), username},
+                {std::string("x-token"), token},
+            };
+        }
+
+        params.emplace(std::string("api-version"),
+                       std::string(API_VERSION.begin(), API_VERSION.end()));
+        if (method != "GET") {
+            params.emplace(std::string("Content-Type"), std::string("application/json"));
         };
-    }
 
-    params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end()));
-    if (method != "GET") {
-        params.emplace(std::string("Content-Type"), std::string("application/json"));
-    };
+        httplib::Request request;
+        request.method = method;
+        request.path = path;
+        request.headers = params;
+        request.body = data;
 
-    httplib::Request request;
-    request.method = method;
-    request.path = path;
-    request.headers = params;
-    request.body = data;
+        httplib::Response response;
 
-    httplib::Response response;
+        if (!cli->send(request, response)) {
+            LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
+            return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
+        }
 
-    if (!cli->send(request, response)) {
-        LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
-        return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
-    }
+        if (response.status >= 400) {
+            LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
+                      response.status);
+            return Common::WebResult{Common::WebResult::Code::HttpError,
+                                     std::to_string(response.status)};
+        }
 
-    if (response.status >= 400) {
-        LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
-                  response.status);
-        return Common::WebResult{Common::WebResult::Code::HttpError,
-                                 std::to_string(response.status)};
-    }
+        auto content_type = response.headers.find("content-type");
 
-    auto content_type = response.headers.find("content-type");
+        if (content_type == response.headers.end()) {
+            LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
+            return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
+        }
 
-    if (content_type == response.headers.end()) {
-        LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
-        return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
+        if (content_type->second.find("application/json") == std::string::npos &&
+            content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
+            LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
+                      content_type->second);
+            return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
+        }
+        return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
     }
 
-    if (content_type->second.find("application/json") == std::string::npos &&
-        content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
-        LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
-                  content_type->second);
-        return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
-    }
-    return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
-}
+    // Retrieve a new JWT from given username and token
+    void UpdateJWT() {
+        if (username.empty() || token.empty()) {
+            return;
+        }
 
-void Client::UpdateJWT() {
-    if (!username.empty() && !token.empty()) {
         auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
         if (result.result_code != Common::WebResult::Code::Success) {
             LOG_ERROR(WebService, "UpdateJWT failed");
@@ -123,27 +158,39 @@ void Client::UpdateJWT() {
             jwt_cache.jwt = jwt = result.returned_data;
         }
     }
-}
 
-Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
-                                      const std::string& data, bool allow_anonymous) {
-    if (jwt.empty()) {
-        UpdateJWT();
-    }
+    std::string host;
+    std::string username;
+    std::string token;
+    std::string jwt;
+    std::unique_ptr<httplib::Client> cli;
+
+    struct JWTCache {
+        std::mutex mutex;
+        std::string username;
+        std::string token;
+        std::string jwt;
+    };
+    static inline JWTCache jwt_cache;
+};
 
-    if (jwt.empty() && !allow_anonymous) {
-        LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
-        return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
-    }
+Client::Client(std::string host, std::string username, std::string token)
+    : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
 
-    auto result = GenericJson(method, path, data, jwt);
-    if (result.result_string == "401") {
-        // Try again with new JWT
-        UpdateJWT();
-        result = GenericJson(method, path, data, jwt);
-    }
+Client::~Client() = default;
+
+Common::WebResult Client::PostJson(const std::string& path, const std::string& data,
+                                   bool allow_anonymous) {
+    return impl->GenericJson("POST", path, data, allow_anonymous);
+}
+
+Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
+    return impl->GenericJson("GET", path, "", allow_anonymous);
+}
 
-    return result;
+Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
+                                     bool allow_anonymous) {
+    return impl->GenericJson("DELETE", path, data, allow_anonymous);
 }
 
 } // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index d75fbcc15509a178e1f9e497e0104f4b47d313f7..c637e09df32e813b6c8dddcdbb08f6bb635dd87c 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -4,23 +4,19 @@
 
 #pragma once
 
-#include <functional>
-#include <mutex>
+#include <memory>
 #include <string>
-#include <tuple>
-#include <httplib.h>
-#include "common/common_types.h"
-#include "common/web_result.h"
 
-namespace httplib {
-class Client;
+namespace Common {
+struct WebResult;
 }
 
 namespace WebService {
 
 class Client {
 public:
-    Client(const std::string& host, const std::string& username, const std::string& token);
+    Client(std::string host, std::string username, std::string token);
+    ~Client();
 
     /**
      * Posts JSON to the specified path.
@@ -30,9 +26,7 @@ public:
      * @return the result of the request.
      */
     Common::WebResult PostJson(const std::string& path, const std::string& data,
-                               bool allow_anonymous) {
-        return GenericJson("POST", path, data, allow_anonymous);
-    }
+                               bool allow_anonymous);
 
     /**
      * Gets JSON from the specified path.
@@ -40,9 +34,7 @@ public:
      * @param allow_anonymous If true, allow anonymous unauthenticated requests.
      * @return the result of the request.
      */
-    Common::WebResult GetJson(const std::string& path, bool allow_anonymous) {
-        return GenericJson("GET", path, "", allow_anonymous);
-    }
+    Common::WebResult GetJson(const std::string& path, bool allow_anonymous);
 
     /**
      * Deletes JSON to the specified path.
@@ -52,41 +44,11 @@ public:
      * @return the result of the request.
      */
     Common::WebResult DeleteJson(const std::string& path, const std::string& data,
-                                 bool allow_anonymous) {
-        return GenericJson("DELETE", path, data, allow_anonymous);
-    }
+                                 bool allow_anonymous);
 
 private:
-    /// A generic function handles POST, GET and DELETE request together
-    Common::WebResult GenericJson(const std::string& method, const std::string& path,
-                                  const std::string& data, bool allow_anonymous);
-
-    /**
-     * A generic function with explicit authentication method specified
-     * JWT is used if the jwt parameter is not empty
-     * username + token is used if jwt is empty but username and token are not empty
-     * anonymous if all of jwt, username and token are empty
-     */
-    Common::WebResult GenericJson(const std::string& method, const std::string& path,
-                                  const std::string& data, const std::string& jwt = "",
-                                  const std::string& username = "", const std::string& token = "");
-
-    // Retrieve a new JWT from given username and token
-    void UpdateJWT();
-
-    std::string host;
-    std::string username;
-    std::string token;
-    std::string jwt;
-    std::unique_ptr<httplib::Client> cli;
-
-    struct JWTCache {
-        std::mutex mutex;
-        std::string username;
-        std::string token;
-        std::string jwt;
-    };
-    static JWTCache jwt_cache;
+    struct Impl;
+    std::unique_ptr<Impl> impl;
 };
 
 } // namespace WebService