diff --git a/include/Settings.h b/include/Settings.h
index 1ef8ea12..3034c1ce 100644
--- a/include/Settings.h
+++ b/include/Settings.h
@@ -86,7 +86,8 @@ struct Settings {
         General_LogChat,
         General_ResourceFolder,
         General_Debug,
-        General_AllowGuests
+        General_AllowGuests,
+        General_InformationPacket,
     };
 
     Sync<std::unordered_map<Key, SettingsTypeVariant>> SettingsMap;
diff --git a/include/THeartbeatThread.h b/include/THeartbeatThread.h
index 5a0c1eec..f81534c1 100644
--- a/include/THeartbeatThread.h
+++ b/include/THeartbeatThread.h
@@ -29,6 +29,7 @@ class THeartbeatThread : public IThreaded {
     //~THeartbeatThread();
     void operator()() override;
 
+    static inline std::string lastCall = "";
 private:
     std::string GenerateCall();
     std::string GetPlayers();
diff --git a/src/LuaAPI.cpp b/src/LuaAPI.cpp
index 582da467..4da07b3d 100644
--- a/src/LuaAPI.cpp
+++ b/src/LuaAPI.cpp
@@ -317,6 +317,14 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) {
             beammp_lua_error("set invalid argument [2] expected string");
         }
         break;
+    case 7: // Information packet
+        if (NewValue.is<bool>()) {
+            Application::Settings.set(Settings::Key::General_InformationPacket, NewValue.as<bool>());
+            beammp_info(std::string("Set `InformationPacket` to ") + (Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false"));
+        } else {
+            beammp_lua_error("set invalid argument [2] expected boolean");
+        }
+         break;
     default:
         beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
         break;
@@ -339,6 +347,8 @@ TLuaValue LuaAPI::MP::Get(int ConfigID) {
         return Application::Settings.getAsString(Settings::Key::General_Name);
     case 6: // Desc
         return Application::Settings.getAsString(Settings::Key::General_Description);
+    case 7: // Information packet
+        return Application::Settings.getAsBool(Settings::Key::General_InformationPacket);
     default:
         beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
         return 0;
diff --git a/src/Settings.cpp b/src/Settings.cpp
index a82a66b8..d2393cd3 100644
--- a/src/Settings.cpp
+++ b/src/Settings.cpp
@@ -34,6 +34,7 @@ Settings::Settings() {
         { General_ResourceFolder, std::string("Resources") },
         { General_Debug, false },
         { General_AllowGuests, true },
+        { General_InformationPacket, true },
         { Misc_SendErrorsShowMessage, true },
         { Misc_SendErrors, true },
         { Misc_ImScaredOfUpdates, true },
@@ -54,6 +55,7 @@ Settings::Settings() {
         { { "General", "ResourceFolder" }, { General_ResourceFolder, READ_ONLY } },
         { { "General", "Debug" }, { General_Debug, READ_WRITE } },
         { { "General", "AllowGuests" }, { General_AllowGuests, READ_WRITE } },
+        { { "General", "InformationPacket" }, { General_InformationPacket, READ_WRITE } },
         { { "Misc", "SendErrorsShowMessage" }, { Misc_SendErrorsShowMessage, READ_WRITE } },
         { { "Misc", "SendErrors" }, { Misc_SendErrors, READ_WRITE } },
         { { "Misc", "ImScaredOfUpdates" }, { Misc_ImScaredOfUpdates, READ_WRITE } },
diff --git a/src/TConfig.cpp b/src/TConfig.cpp
index c556b042..66c0eff9 100644
--- a/src/TConfig.cpp
+++ b/src/TConfig.cpp
@@ -56,6 +56,8 @@ static constexpr std::string_view StrLogChat = "LogChat";
 static constexpr std::string_view EnvStrLogChat = "BEAMMP_LOG_CHAT";
 static constexpr std::string_view StrAllowGuests = "AllowGuests";
 static constexpr std::string_view EnvStrAllowGuests = "BEAMMP_ALLOW_GUESTS";
+static constexpr std::string_view StrInformationPacket = "InformationPacket";
+static constexpr std::string_view EnvStrInformationPacket = "BEAMMP_INFORMATION_PACKET";
 static constexpr std::string_view StrPassword = "Password";
 
 // Misc
@@ -132,6 +134,8 @@ void TConfig::FlushToFile() {
     SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
     data["General"][StrDebug.data()] = Application::Settings.getAsBool(Settings::Key::General_Debug);
     data["General"][StrPrivate.data()] = Application::Settings.getAsBool(Settings::Key::General_Private);
+    SetComment(data["General"][StrInformationPacket.data()].comments(), " Whether to allow unconnected clients to get the public server information without joining");
+    data["General"][StrInformationPacket.data()] = Application::Settings.getAsBool(Settings::Key::General_InformationPacket);
     data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests);
     SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests");
     data["General"][StrPort.data()] = Application::Settings.getAsInt(Settings::Key::General_Port);
@@ -248,6 +252,7 @@ void TConfig::ParseFromFile(std::string_view name) {
         // Read into new Settings Singleton
         TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug);
         TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private);
+        TryReadValue(data, "General", StrInformationPacket, EnvStrInformationPacket, Settings::Key::General_InformationPacket);
         TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port);
         TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Settings::Key::General_MaxCars);
         TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers);
@@ -299,6 +304,7 @@ void TConfig::PrintDebug() {
     }
     beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Debug) ? "true" : "false"));
     beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false"));
+    beammp_debug(std::string(StrInformationPacket) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false"));
     beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)));
     beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars)));
     beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)));
diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp
index 303cc9b1..057d70da 100644
--- a/src/THeartbeatThread.cpp
+++ b/src/THeartbeatThread.cpp
@@ -23,6 +23,7 @@
 #include "Common.h"
 #include "Http.h"
 // #include "SocketIO.h"
+#include <nlohmann/json.hpp>
 #include <rapidjson/document.h>
 #include <rapidjson/rapidjson.h>
 #include <sstream>
@@ -65,7 +66,7 @@ void THeartbeatThread::operator()() {
         json::Document Doc;
         bool Ok = false;
         for (const auto& Url : Application::GetBackendUrlsInOrder()) {
-            T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
+            T = Http::POST(Url, 443, Target, Body, "application/json", &ResponseCode, { { "api-v", "2" } });
             Doc.Parse(T.data(), T.size());
             if (Doc.HasParseError() || !Doc.IsObject()) {
                 if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
@@ -138,25 +139,30 @@ void THeartbeatThread::operator()() {
 }
 
 std::string THeartbeatThread::GenerateCall() {
-    std::stringstream Ret;
+    nlohmann::json Ret = {
+        { "players", mServer.ClientCount() },
+        { "maxplayers", Application::Settings.getAsInt(Settings::Key::General_MaxPlayers) },
+        { "port", Application::Settings.getAsInt(Settings::Key::General_Port) },
+        { "map", Application::Settings.getAsString(Settings::Key::General_Map) },
+        { "private", Application::Settings.getAsBool(Settings::Key::General_Private) },
+        { "version", Application::ServerVersionString() },
+        { "clientversion", Application::ClientMinimumVersion().AsString() },
+        { "name", Application::Settings.getAsString(Settings::Key::General_Name) },
+        { "tags", Application::Settings.getAsString(Settings::Key::General_Tags) },
+        { "guests", Application::Settings.getAsBool(Settings::Key::General_AllowGuests) },
+        { "modlist", mResourceManager.TrimmedList() },
+        { "modstotalsize", mResourceManager.MaxModSize() },
+        { "modstotal", mResourceManager.ModsLoaded() },
+        { "playerslist", GetPlayers() },
+        { "desc", Application::Settings.getAsString(Settings::Key::General_Description) }
+    };
 
-    Ret << "uuid=" << Application::Settings.getAsString(Settings::Key::General_AuthKey)
-        << "&players=" << mServer.ClientCount()
-        << "&maxplayers=" << Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)
-        << "&port=" << Application::Settings.getAsInt(Settings::Key::General_Port)
-        << "&map=" << Application::Settings.getAsString(Settings::Key::General_Map)
-        << "&private=" << (Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false")
-        << "&version=" << Application::ServerVersionString()
-        << "&clientversion=" << Application::ClientMinimumVersion().AsString()
-        << "&name=" << Application::Settings.getAsString(Settings::Key::General_Name)
-        << "&tags=" << Application::Settings.getAsString(Settings::Key::General_Tags)
-        << "&guests=" << (Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false")
-        << "&modlist=" << mResourceManager.TrimmedList()
-        << "&modstotalsize=" << mResourceManager.MaxModSize()
-        << "&modstotal=" << mResourceManager.ModsLoaded()
-        << "&playerslist=" << GetPlayers()
-        << "&desc=" << Application::Settings.getAsString(Settings::Key::General_Description);
-    return Ret.str();
+    lastCall = Ret.dump();
+
+    // Add sensitive information here because value of lastCall is used for the information packet.
+    Ret["uuid"] = Application::Settings.getAsString(Settings::Key::General_AuthKey);
+
+    return Ret.dump();
 }
 THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server)
     : mResourceManager(ResourceManager)
diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp
index 42440b11..fb3f4c69 100644
--- a/src/TLuaEngine.cpp
+++ b/src/TLuaEngine.cpp
@@ -1024,7 +1024,8 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
         "MaxPlayers", 3,
         "Map", 4,
         "Name", 5,
-        "Description", 6);
+        "Description", 6,
+        "InformationPacket", 7);
 
     MPTable.create_named("CallStrategy",
         "BestEffort", CallStrategy::BestEffort,
diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp
index f89035a4..6fc0b875 100644
--- a/src/TNetwork.cpp
+++ b/src/TNetwork.cpp
@@ -20,6 +20,7 @@
 #include "Client.h"
 #include "Common.h"
 #include "LuaAPI.h"
+#include "THeartbeatThread.h"
 #include "TLuaEngine.h"
 #include "TScopedTimer.h"
 #include "nlohmann/json.hpp"
@@ -246,6 +247,9 @@ void TNetwork::Identify(TConnection&& RawConnection) {
             boost::system::error_code ec;
             write(RawConnection.Socket, buffer("P"), ec);
             return;
+        } else if (Code == 'I') {
+            boost::system::error_code ec;
+            write(RawConnection.Socket, buffer(Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? THeartbeatThread::lastCall : ""), ec);
         } else {
             beammp_errorf("Invalid code got in Identify: '{}'", Code);
         }