diff --git a/src/common/NetworkPrivate.cpp b/src/common/NetworkPrivate.cpp
index 44cb87102..f0a74ed5a 100644
--- a/src/common/NetworkPrivate.cpp
+++ b/src/common/NetworkPrivate.cpp
@@ -72,12 +72,12 @@ void writeToCache(const std::shared_ptr<NetworkData> &data,
     }
 }
 
-void loadUncached(const std::shared_ptr<NetworkData> &data)
+void loadUncached(std::shared_ptr<NetworkData> &&data)
 {
     DebugCount::increase("http request started");
 
     NetworkRequester requester;
-    NetworkWorker *worker = new NetworkWorker;
+    auto *worker = new NetworkWorker;
 
     worker->moveToThread(&NetworkManager::workerThread);
 
@@ -89,7 +89,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
             data->timer_->start(data->timeoutMS_);
         }
 
-        auto reply = [&]() -> QNetworkReply * {
+        auto *reply = [&]() -> QNetworkReply * {
             switch (data->requestType_)
             {
                 case NetworkRequestType::Get:
@@ -245,12 +245,16 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
             if (data->onSuccess_)
             {
                 if (data->executeConcurrently_)
+                {
                     QtConcurrent::run([onSuccess = std::move(data->onSuccess_),
                                        result = std::move(result)] {
                         onSuccess(result);
                     });
+                }
                 else
+                {
                     data->onSuccess_(result);
+                }
             }
             // log("finished {}", data->request_.url().toString());
 
@@ -276,11 +280,15 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
             if (data->finally_)
             {
                 if (data->executeConcurrently_)
+                {
                     QtConcurrent::run([finally = std::move(data->finally_)] {
                         finally();
                     });
+                }
                 else
+                {
                     data->finally_();
+                }
             }
         };
 
@@ -316,87 +324,87 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
 }
 
 // First tried to load cached, then uncached.
-void loadCached(const std::shared_ptr<NetworkData> &data)
+void loadCached(std::shared_ptr<NetworkData> &&data)
 {
     QFile cachedFile(getPaths()->cacheDirectory() + "/" + data->getHash());
 
     if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly))
     {
         // File didn't exist OR File could not be opened
-        loadUncached(data);
+        loadUncached(std::move(data));
         return;
     }
-    else
-    {
-        // XXX: check if bytes is empty?
-        QByteArray bytes = cachedFile.readAll();
-        NetworkResult result(bytes, 200);
 
-        qCDebug(chatterinoHTTP)
-            << QString("%1 [CACHED] 200 %2")
-                   .arg(networkRequestTypes.at(int(data->requestType_)),
-                        data->request_.url().toString());
-        if (data->onSuccess_)
+    // XXX: check if bytes is empty?
+    QByteArray bytes = cachedFile.readAll();
+    NetworkResult result(bytes, 200);
+
+    qCDebug(chatterinoHTTP)
+        << QString("%1 [CACHED] 200 %2")
+               .arg(networkRequestTypes.at(int(data->requestType_)),
+                    data->request_.url().toString());
+    if (data->onSuccess_)
+    {
+        if (data->executeConcurrently_ || isGuiThread())
         {
-            if (data->executeConcurrently_ || isGuiThread())
+            // XXX: If outcome is Failure, we should invalidate the cache file
+            // somehow/somewhere
+            /*auto outcome =*/
+            if (data->hasCaller_ && !data->caller_.get())
             {
-                // XXX: If outcome is Failure, we should invalidate the cache file
-                // somehow/somewhere
-                /*auto outcome =*/
+                return;
+            }
+            data->onSuccess_(result);
+        }
+        else
+        {
+            postToThread([data, result]() {
                 if (data->hasCaller_ && !data->caller_.get())
                 {
                     return;
                 }
+
                 data->onSuccess_(result);
-            }
-            else
-            {
-                postToThread([data, result]() {
-                    if (data->hasCaller_ && !data->caller_.get())
-                    {
-                        return;
-                    }
-
-                    data->onSuccess_(result);
-                });
-            }
+            });
         }
+    }
 
-        if (data->finally_)
+    if (data->finally_)
+    {
+        if (data->executeConcurrently_ || isGuiThread())
         {
-            if (data->executeConcurrently_ || isGuiThread())
+            if (data->hasCaller_ && !data->caller_.get())
             {
+                return;
+            }
+
+            data->finally_();
+        }
+        else
+        {
+            postToThread([data]() {
                 if (data->hasCaller_ && !data->caller_.get())
                 {
                     return;
                 }
 
                 data->finally_();
-            }
-            else
-            {
-                postToThread([data]() {
-                    if (data->hasCaller_ && !data->caller_.get())
-                    {
-                        return;
-                    }
-
-                    data->finally_();
-                });
-            }
+            });
         }
     }
 }
 
-void load(const std::shared_ptr<NetworkData> &data)
+void load(std::shared_ptr<NetworkData> &&data)
 {
     if (data->cache_)
     {
-        QtConcurrent::run(loadCached, data);
+        QtConcurrent::run([data = std::move(data)]() mutable {
+            loadCached(std::move(data));
+        });
     }
     else
     {
-        loadUncached(data);
+        loadUncached(std::move(data));
     }
 }
 
diff --git a/src/common/NetworkPrivate.hpp b/src/common/NetworkPrivate.hpp
index 03d4a705e..3fe841bc2 100644
--- a/src/common/NetworkPrivate.hpp
+++ b/src/common/NetworkPrivate.hpp
@@ -68,6 +68,6 @@ private:
     QString hash_;
 };
 
-void load(const std::shared_ptr<NetworkData> &data);
+void load(std::shared_ptr<NetworkData> &&data);
 
 }  // namespace chatterino
diff --git a/src/common/NetworkRequest.cpp b/src/common/NetworkRequest.cpp
index cfe0cd177..d856faf1e 100644
--- a/src/common/NetworkRequest.cpp
+++ b/src/common/NetworkRequest.cpp
@@ -1,14 +1,8 @@
 #include "common/NetworkRequest.hpp"
 
 #include "common/NetworkPrivate.hpp"
-#include "common/Outcome.hpp"
 #include "common/QLogging.hpp"
 #include "common/Version.hpp"
-#include "debug/AssertInGuiThread.hpp"
-#include "providers/twitch/TwitchCommon.hpp"
-#include "singletons/Paths.hpp"
-#include "util/DebugCount.hpp"
-#include "util/PostToThread.hpp"
 
 #include <QDebug>
 #include <QFile>
@@ -28,7 +22,7 @@ NetworkRequest::NetworkRequest(const std::string &url,
     this->initializeDefaultValues();
 }
 
-NetworkRequest::NetworkRequest(QUrl url, NetworkRequestType requestType)
+NetworkRequest::NetworkRequest(const QUrl &url, NetworkRequestType requestType)
     : data(new NetworkData)
 {
     this->data->request_.setUrl(url);
@@ -37,10 +31,7 @@ NetworkRequest::NetworkRequest(QUrl url, NetworkRequestType requestType)
     this->initializeDefaultValues();
 }
 
-NetworkRequest::~NetworkRequest()
-{
-    //assert(!this->data || this->executed_);
-}
+NetworkRequest::~NetworkRequest() = default;
 
 NetworkRequest NetworkRequest::type(NetworkRequestType newRequestType) &&
 {
@@ -63,25 +54,25 @@ NetworkRequest NetworkRequest::caller(const QObject *caller) &&
 
 NetworkRequest NetworkRequest::onReplyCreated(NetworkReplyCreatedCallback cb) &&
 {
-    this->data->onReplyCreated_ = cb;
+    this->data->onReplyCreated_ = std::move(cb);
     return std::move(*this);
 }
 
 NetworkRequest NetworkRequest::onError(NetworkErrorCallback cb) &&
 {
-    this->data->onError_ = cb;
+    this->data->onError_ = std::move(cb);
     return std::move(*this);
 }
 
 NetworkRequest NetworkRequest::onSuccess(NetworkSuccessCallback cb) &&
 {
-    this->data->onSuccess_ = cb;
+    this->data->onSuccess_ = std::move(cb);
     return std::move(*this);
 }
 
 NetworkRequest NetworkRequest::finally(NetworkFinallyCallback cb) &&
 {
-    this->data->finally_ = cb;
+    this->data->finally_ = std::move(cb);
     return std::move(*this);
 }
 
@@ -106,6 +97,13 @@ NetworkRequest NetworkRequest::header(const char *headerName,
     return std::move(*this);
 }
 
+NetworkRequest NetworkRequest::header(QNetworkRequest::KnownHeaders header,
+                                      const QVariant &value) &&
+{
+    this->data->request_.setHeader(header, value);
+    return std::move(*this);
+}
+
 NetworkRequest NetworkRequest::headerList(
     const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&
 {
@@ -129,20 +127,6 @@ NetworkRequest NetworkRequest::concurrent() &&
     return std::move(*this);
 }
 
-NetworkRequest NetworkRequest::authorizeTwitchV5(const QString &clientID,
-                                                 const QString &oauthToken) &&
-{
-    // TODO: make two overloads, with and without oauth token
-    auto tmp = std::move(*this)
-                   .header("Client-ID", clientID)
-                   .header("Accept", "application/vnd.twitchtv.v5+json");
-
-    if (!oauthToken.isEmpty())
-        return std::move(tmp).header("Authorization", "OAuth " + oauthToken);
-    else
-        return tmp;
-}
-
 NetworkRequest NetworkRequest::multiPart(QHttpMultiPart *payload) &&
 {
     payload->setParent(this->data->lifetimeManager_);
@@ -207,10 +191,28 @@ void NetworkRequest::initializeDefaultValues()
     this->data->request_.setRawHeader("User-Agent", userAgent);
 }
 
-// Helper creator functions
-NetworkRequest NetworkRequest::twitchRequest(QUrl url)
+NetworkRequest NetworkRequest::json(const QJsonArray &root) &&
 {
-    return NetworkRequest(url).authorizeTwitchV5(getDefaultClientID());
+    return std::move(*this).json(QJsonDocument(root));
+}
+
+NetworkRequest NetworkRequest::json(const QJsonObject &root) &&
+{
+    return std::move(*this).json(QJsonDocument(root));
+}
+
+NetworkRequest NetworkRequest::json(const QJsonDocument &document) &&
+{
+    return std::move(*this).json(document.toJson(QJsonDocument::Compact));
+}
+
+NetworkRequest NetworkRequest::json(const QByteArray &payload) &&
+{
+    return std::move(*this)
+        .payload(payload)
+        .header(QNetworkRequest::ContentTypeHeader, "application/json")
+        .header(QNetworkRequest::ContentLengthHeader, payload.length())
+        .header("Accept", "application/json");
 }
 
 }  // namespace chatterino
diff --git a/src/common/NetworkRequest.hpp b/src/common/NetworkRequest.hpp
index 85f34782a..9d55cf1b7 100644
--- a/src/common/NetworkRequest.hpp
+++ b/src/common/NetworkRequest.hpp
@@ -6,6 +6,10 @@
 
 #include <memory>
 
+class QJsonArray;
+class QJsonObject;
+class QJsonDocument;
+
 namespace chatterino {
 
 struct NetworkData;
@@ -24,8 +28,8 @@ public:
     explicit NetworkRequest(
         const std::string &url,
         NetworkRequestType requestType = NetworkRequestType::Get);
-    explicit NetworkRequest(
-        QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
+    explicit NetworkRequest(const QUrl &url, NetworkRequestType requestType =
+                                                 NetworkRequestType::Get);
 
     // Enable move
     NetworkRequest(NetworkRequest &&other) = default;
@@ -54,23 +58,25 @@ public:
     NetworkRequest header(const char *headerName, const char *value) &&;
     NetworkRequest header(const char *headerName, const QByteArray &value) &&;
     NetworkRequest header(const char *headerName, const QString &value) &&;
+    NetworkRequest header(QNetworkRequest::KnownHeaders header,
+                          const QVariant &value) &&;
     NetworkRequest headerList(
         const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&;
     NetworkRequest timeout(int ms) &&;
     NetworkRequest concurrent() &&;
-    NetworkRequest authorizeTwitchV5(const QString &clientID,
-                                     const QString &oauthToken = QString()) &&;
     NetworkRequest multiPart(QHttpMultiPart *payload) &&;
     /**
      * This will change `RedirectPolicyAttribute`.
      * `QNetworkRequest`'s defaults are used by default (Qt 5: no-follow, Qt 6: follow).
      */
     NetworkRequest followRedirects(bool on) &&;
+    NetworkRequest json(const QJsonObject &root) &&;
+    NetworkRequest json(const QJsonArray &root) &&;
+    NetworkRequest json(const QJsonDocument &document) &&;
+    NetworkRequest json(const QByteArray &payload) &&;
 
     void execute();
 
-    static NetworkRequest twitchRequest(QUrl url);
-
 private:
     void initializeDefaultValues();
 };
diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp
index 4dc4f1222..662484c98 100644
--- a/src/providers/twitch/api/Helix.cpp
+++ b/src/providers/twitch/api/Helix.cpp
@@ -52,7 +52,7 @@ void Helix::fetchUsers(QStringList userIds, QStringList userLogins,
     }
 
     // TODO: set on success and on error
-    this->makeRequest("users", urlQuery)
+    this->makeGet("users", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             auto data = root.value("data");
@@ -142,7 +142,7 @@ void Helix::fetchUsersFollows(
     }
 
     // TODO: set on success and on error
-    this->makeRequest("users/follows", urlQuery)
+    this->makeGet("users/follows", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             if (root.empty())
@@ -186,7 +186,7 @@ void Helix::fetchStreams(
     }
 
     // TODO: set on success and on error
-    this->makeRequest("streams", urlQuery)
+    this->makeGet("streams", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             auto data = root.value("data");
@@ -279,7 +279,7 @@ void Helix::fetchGames(QStringList gameIds, QStringList gameNames,
     }
 
     // TODO: set on success and on error
-    this->makeRequest("games", urlQuery)
+    this->makeGet("games", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             auto data = root.value("data");
@@ -315,7 +315,7 @@ void Helix::searchGames(QString gameName,
     QUrlQuery urlQuery;
     urlQuery.addQueryItem("query", gameName);
 
-    this->makeRequest("search/categories", urlQuery)
+    this->makeGet("search/categories", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             auto data = root.value("data");
@@ -372,8 +372,7 @@ void Helix::createClip(QString channelId,
     QUrlQuery urlQuery;
     urlQuery.addQueryItem("broadcaster_id", channelId);
 
-    this->makeRequest("clips", urlQuery)
-        .type(NetworkRequestType::Post)
+    this->makePost("clips", urlQuery)
         .header("Content-Type", "application/json")
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
@@ -425,7 +424,7 @@ void Helix::getChannel(QString broadcasterId,
     QUrlQuery urlQuery;
     urlQuery.addQueryItem("broadcaster_id", broadcasterId);
 
-    this->makeRequest("channels", urlQuery)
+    this->makeGet("channels", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             auto data = root.value("data");
@@ -460,10 +459,8 @@ void Helix::createStreamMarker(
     }
     payload.insert("user_id", QJsonValue(broadcasterId));
 
-    this->makeRequest("streams/markers", QUrlQuery())
-        .type(NetworkRequestType::Post)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
+    this->makePost("streams/markers", QUrlQuery())
+        .json(payload)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             auto data = root.value("data");
@@ -515,7 +512,7 @@ void Helix::loadBlocks(QString userId,
     urlQuery.addQueryItem("broadcaster_id", userId);
     urlQuery.addQueryItem("first", "100");
 
-    this->makeRequest("users/blocks", urlQuery)
+    this->makeGet("users/blocks", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             auto data = root.value("data");
@@ -551,8 +548,7 @@ void Helix::blockUser(QString targetUserId,
     QUrlQuery urlQuery;
     urlQuery.addQueryItem("target_user_id", targetUserId);
 
-    this->makeRequest("users/blocks", urlQuery)
-        .type(NetworkRequestType::Put)
+    this->makePut("users/blocks", urlQuery)
         .onSuccess([successCallback](auto /*result*/) -> Outcome {
             successCallback();
             return Success;
@@ -571,8 +567,7 @@ void Helix::unblockUser(QString targetUserId,
     QUrlQuery urlQuery;
     urlQuery.addQueryItem("target_user_id", targetUserId);
 
-    this->makeRequest("users/blocks", urlQuery)
-        .type(NetworkRequestType::Delete)
+    this->makeDelete("users/blocks", urlQuery)
         .onSuccess([successCallback](auto /*result*/) -> Outcome {
             successCallback();
             return Success;
@@ -590,7 +585,6 @@ void Helix::updateChannel(QString broadcasterId, QString gameId,
                           HelixFailureCallback failureCallback)
 {
     QUrlQuery urlQuery;
-    auto data = QJsonDocument();
     auto obj = QJsonObject();
     if (!gameId.isEmpty())
     {
@@ -611,12 +605,9 @@ void Helix::updateChannel(QString broadcasterId, QString gameId,
         return;
     }
 
-    data.setObject(obj);
     urlQuery.addQueryItem("broadcaster_id", broadcasterId);
-    this->makeRequest("channels", urlQuery)
-        .type(NetworkRequestType::Patch)
-        .header("Content-Type", "application/json")
-        .payload(data.toJson())
+    this->makePatch("channels", urlQuery)
+        .json(obj)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             successCallback(result);
             return Success;
@@ -638,10 +629,8 @@ void Helix::manageAutoModMessages(
     payload.insert("msg_id", msgID);
     payload.insert("action", action);
 
-    this->makeRequest("moderation/automod/message", QUrlQuery())
-        .type(NetworkRequestType::Post)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
+    this->makePost("moderation/automod/message", QUrlQuery())
+        .json(payload)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             successCallback();
             return Success;
@@ -697,7 +686,7 @@ void Helix::getCheermotes(
 
     urlQuery.addQueryItem("broadcaster_id", broadcasterId);
 
-    this->makeRequest("bits/cheermotes", urlQuery)
+    this->makeGet("bits/cheermotes", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto root = result.parseJson();
             auto data = root.value("data");
@@ -735,7 +724,7 @@ void Helix::getEmoteSetData(QString emoteSetId,
 
     urlQuery.addQueryItem("emote_set_id", emoteSetId);
 
-    this->makeRequest("chat/emotes/set", urlQuery)
+    this->makeGet("chat/emotes/set", urlQuery)
         .onSuccess([successCallback, failureCallback,
                     emoteSetId](auto result) -> Outcome {
             QJsonObject root = result.parseJson();
@@ -767,7 +756,7 @@ void Helix::getChannelEmotes(
     QUrlQuery urlQuery;
     urlQuery.addQueryItem("broadcaster_id", broadcasterId);
 
-    this->makeRequest("chat/emotes", urlQuery)
+    this->makeGet("chat/emotes", urlQuery)
         .onSuccess([successCallback,
                     failureCallback](NetworkResult result) -> Outcome {
             QJsonObject root = result.parseJson();
@@ -807,10 +796,8 @@ void Helix::updateUserChatColor(
     payload.insert("user_id", QJsonValue(userID));
     payload.insert("color", QJsonValue(color));
 
-    this->makeRequest("chat/color", QUrlQuery())
-        .type(NetworkRequestType::Put)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
+    this->makePut("chat/color", QUrlQuery())
+        .json(payload)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto obj = result.parseJson();
             if (result.status() != 204)
@@ -887,8 +874,7 @@ void Helix::deleteChatMessages(
         urlQuery.addQueryItem("message_id", messageID);
     }
 
-    this->makeRequest("moderation/chat", urlQuery)
-        .type(NetworkRequestType::Delete)
+    this->makeDelete("moderation/chat", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -966,8 +952,7 @@ void Helix::addChannelModerator(
     urlQuery.addQueryItem("broadcaster_id", broadcasterID);
     urlQuery.addQueryItem("user_id", userID);
 
-    this->makeRequest("moderation/moderators", urlQuery)
-        .type(NetworkRequestType::Post)
+    this->makePost("moderation/moderators", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -1055,8 +1040,7 @@ void Helix::removeChannelModerator(
     urlQuery.addQueryItem("broadcaster_id", broadcasterID);
     urlQuery.addQueryItem("user_id", userID);
 
-    this->makeRequest("moderation/moderators", urlQuery)
-        .type(NetworkRequestType::Delete)
+    this->makeDelete("moderation/moderators", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -1142,10 +1126,8 @@ void Helix::sendChatAnnouncement(
         std::string{magic_enum::enum_name<HelixAnnouncementColor>(color)};
     body.insert("color", QString::fromStdString(colorStr).toLower());
 
-    this->makeRequest("chat/announcements", urlQuery)
-        .type(NetworkRequestType::Post)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(body).toJson(QJsonDocument::Compact))
+    this->makePost("chat/announcements", urlQuery)
+        .json(body)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -1214,8 +1196,7 @@ void Helix::addChannelVIP(
     urlQuery.addQueryItem("broadcaster_id", broadcasterID);
     urlQuery.addQueryItem("user_id", userID);
 
-    this->makeRequest("channels/vips", urlQuery)
-        .type(NetworkRequestType::Post)
+    this->makePost("channels/vips", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -1293,8 +1274,7 @@ void Helix::removeChannelVIP(
     urlQuery.addQueryItem("broadcaster_id", broadcasterID);
     urlQuery.addQueryItem("user_id", userID);
 
-    this->makeRequest("channels/vips", urlQuery)
-        .type(NetworkRequestType::Delete)
+    this->makeDelete("channels/vips", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -1383,8 +1363,7 @@ void Helix::unbanUser(
     urlQuery.addQueryItem("moderator_id", moderatorID);
     urlQuery.addQueryItem("user_id", userID);
 
-    this->makeRequest("moderation/bans", urlQuery)
-        .type(NetworkRequestType::Delete)
+    this->makeDelete("moderation/bans", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -1489,8 +1468,7 @@ void Helix::startRaid(
     urlQuery.addQueryItem("from_broadcaster_id", fromBroadcasterID);
     urlQuery.addQueryItem("to_broadcaster_id", toBroadcasterID);
 
-    this->makeRequest("raids", urlQuery)
-        .type(NetworkRequestType::Post)
+    this->makePost("raids", urlQuery)
         .onSuccess(
             [successCallback, failureCallback](auto /*result*/) -> Outcome {
                 successCallback();
@@ -1570,8 +1548,7 @@ void Helix::cancelRaid(
 
     urlQuery.addQueryItem("broadcaster_id", broadcasterID);
 
-    this->makeRequest("raids", urlQuery)
-        .type(NetworkRequestType::Delete)
+    this->makeDelete("raids", urlQuery)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -1731,10 +1708,8 @@ void Helix::updateChatSettings(
     urlQuery.addQueryItem("broadcaster_id", broadcasterID);
     urlQuery.addQueryItem("moderator_id", moderatorID);
 
-    this->makeRequest("chat/settings", urlQuery)
-        .type(NetworkRequestType::Patch)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
+    this->makePatch("chat/settings", urlQuery)
+        .json(payload)
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 200)
             {
@@ -1857,7 +1832,7 @@ void Helix::fetchChatters(
         urlQuery.addQueryItem("after", after);
     }
 
-    this->makeRequest("chat/chatters", urlQuery)
+    this->makeGet("chat/chatters", urlQuery)
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 200)
             {
@@ -1966,7 +1941,7 @@ void Helix::fetchModerators(
         urlQuery.addQueryItem("after", after);
     }
 
-    this->makeRequest("moderation/moderators", urlQuery)
+    this->makeGet("moderation/moderators", urlQuery)
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 200)
             {
@@ -2051,10 +2026,8 @@ void Helix::banUser(QString broadcasterID, QString moderatorID, QString userID,
         payload["data"] = data;
     }
 
-    this->makeRequest("moderation/bans", urlQuery)
-        .type(NetworkRequestType::Post)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
+    this->makePost("moderation/bans", urlQuery)
+        .json(payload)
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 200)
             {
@@ -2150,10 +2123,8 @@ void Helix::sendWhisper(
     QJsonObject payload;
     payload["message"] = message;
 
-    this->makeRequest("whispers", urlQuery)
-        .type(NetworkRequestType::Post)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
+    this->makePost("whispers", urlQuery)
+        .json(payload)
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 204)
             {
@@ -2295,8 +2266,7 @@ void Helix::getChannelVIPs(
     //   as the mod list can go over 100 (I assume, I see no limit)
     urlQuery.addQueryItem("first", "100");
 
-    this->makeRequest("channels/vips", urlQuery)
-        .type(NetworkRequestType::Get)
+    this->makeGet("channels/vips", urlQuery)
         .header("Content-Type", "application/json")
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 200)
@@ -2383,10 +2353,8 @@ void Helix::startCommercial(
     payload.insert("broadcaster_id", QJsonValue(broadcasterID));
     payload.insert("length", QJsonValue(length));
 
-    this->makeRequest("channels/commercial", QUrlQuery())
-        .type(NetworkRequestType::Post)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
+    this->makePost("channels/commercial", QUrlQuery())
+        .json(payload)
         .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
             auto obj = result.parseJson();
             if (obj.isEmpty())
@@ -2476,7 +2444,7 @@ void Helix::getGlobalBadges(
 {
     using Error = HelixGetGlobalBadgesError;
 
-    this->makeRequest("chat/badges/global", QUrlQuery())
+    this->makeGet("chat/badges/global", QUrlQuery())
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 200)
             {
@@ -2523,7 +2491,7 @@ void Helix::getChannelBadges(
     QUrlQuery urlQuery;
     urlQuery.addQueryItem("broadcaster_id", broadcasterID);
 
-    this->makeRequest("chat/badges", urlQuery)
+    this->makeGet("chat/badges", urlQuery)
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 200)
             {
@@ -2575,10 +2543,8 @@ void Helix::updateShieldMode(
     QJsonObject payload;
     payload["is_active"] = isActive;
 
-    this->makeRequest("moderation/shield_mode", urlQuery)
-        .type(NetworkRequestType::Put)
-        .header("Content-Type", "application/json")
-        .payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
+    this->makePut("moderation/shield_mode", urlQuery)
+        .json(payload)
         .onSuccess([successCallback](auto result) -> Outcome {
             if (result.status() != 200)
             {
@@ -2631,7 +2597,8 @@ void Helix::updateShieldMode(
         .execute();
 }
 
-NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
+NetworkRequest Helix::makeRequest(const QString &url, const QUrlQuery &urlQuery,
+                                  NetworkRequestType type)
 {
     assert(!url.startsWith("/"));
 
@@ -2655,13 +2622,38 @@ NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
 
     fullUrl.setQuery(urlQuery);
 
-    return NetworkRequest(fullUrl)
+    return NetworkRequest(fullUrl, type)
         .timeout(5 * 1000)
         .header("Accept", "application/json")
         .header("Client-ID", this->clientId)
         .header("Authorization", "Bearer " + this->oauthToken);
 }
 
+NetworkRequest Helix::makeGet(const QString &url, const QUrlQuery &urlQuery)
+{
+    return this->makeRequest(url, urlQuery, NetworkRequestType::Get);
+}
+
+NetworkRequest Helix::makeDelete(const QString &url, const QUrlQuery &urlQuery)
+{
+    return this->makeRequest(url, urlQuery, NetworkRequestType::Delete);
+}
+
+NetworkRequest Helix::makePost(const QString &url, const QUrlQuery &urlQuery)
+{
+    return this->makeRequest(url, urlQuery, NetworkRequestType::Post);
+}
+
+NetworkRequest Helix::makePut(const QString &url, const QUrlQuery &urlQuery)
+{
+    return this->makeRequest(url, urlQuery, NetworkRequestType::Put);
+}
+
+NetworkRequest Helix::makePatch(const QString &url, const QUrlQuery &urlQuery)
+{
+    return this->makeRequest(url, urlQuery, NetworkRequestType::Patch);
+}
+
 void Helix::update(QString clientId, QString oauthToken)
 {
     this->clientId = std::move(clientId);
diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp
index 0492b0560..f6dcc8f02 100644
--- a/src/providers/twitch/api/Helix.hpp
+++ b/src/providers/twitch/api/Helix.hpp
@@ -1361,7 +1361,13 @@ protected:
         FailureCallback<HelixGetModeratorsError, QString> failureCallback);
 
 private:
-    NetworkRequest makeRequest(QString url, QUrlQuery urlQuery);
+    NetworkRequest makeRequest(const QString &url, const QUrlQuery &urlQuery,
+                               NetworkRequestType type);
+    NetworkRequest makeGet(const QString &url, const QUrlQuery &urlQuery);
+    NetworkRequest makeDelete(const QString &url, const QUrlQuery &urlQuery);
+    NetworkRequest makePost(const QString &url, const QUrlQuery &urlQuery);
+    NetworkRequest makePut(const QString &url, const QUrlQuery &urlQuery);
+    NetworkRequest makePatch(const QString &url, const QUrlQuery &urlQuery);
 
     QString clientId;
     QString oauthToken;