diff --git a/include/opendht/dht_proxy_server.h b/include/opendht/dht_proxy_server.h
index 0c00493850445467849d4823d0f59fece8a778ed..272cc975d7029d52fc2337e79c7658bb06f2788d 100644
--- a/include/opendht/dht_proxy_server.h
+++ b/include/opendht/dht_proxy_server.h
@@ -40,7 +40,8 @@ namespace dht {
 enum class PushType {
     None = 0,
     Android,
-    iOS
+    iOS,
+    UnifiedPush
 };
 }
 MSGPACK_ADD_ENUM(dht::PushType)
@@ -65,6 +66,7 @@ struct OPENDHT_PUBLIC ProxyServerConfig {
     std::string address {};
     in_port_t port {8000};
     std::string pushServer {};
+    std::string unifiedPushEndpoint {};
     std::string persistStatePath {};
     dht::crypto::Identity identity {};
     std::string bundleId {};
@@ -424,6 +426,7 @@ private:
     mutable std::atomic<time_point> lastStatsReset_ {time_point::min()};
 
     std::string pushServer_;
+    std::string unifiedPushEndpoint_;
     std::string bundleId_;
 
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
diff --git a/src/dht_proxy_server.cpp b/src/dht_proxy_server.cpp
index 3fe2aae45f7f8229b4f6e1185e1234c1722e3f98..c697fe73117b1749dad012bd906c2ed385cf1830 100644
--- a/src/dht_proxy_server.cpp
+++ b/src/dht_proxy_server.cpp
@@ -212,6 +212,7 @@ DhtProxyServer::DhtProxyServer(const std::shared_ptr<DhtRunner>& dht,
         printStatsTimer_(std::make_unique<asio::steady_timer>(*ioContext_, 3s)),
         connListener_(std::make_shared<ConnectionListener>(std::bind(&DhtProxyServer::onConnectionClosed, this, std::placeholders::_1))),
         pushServer_(config.pushServer),
+        unifiedPushEndpoint_(config.unifiedPushEndpoint),
         bundleId_(config.bundleId)
 {
     if (not dht_)
@@ -762,6 +763,8 @@ DhtProxyServer::getTypeFromString(const std::string& type) {
         return PushType::Android;
     else if (type == "ios")
         return PushType::iOS;
+    else if (type == "unifiedpush")
+        return PushType::UnifiedPush;
     return PushType::None;
 }
 
@@ -1012,55 +1015,65 @@ DhtProxyServer::handleCancelPushListen(const asio::error_code &ec, const std::st
 void
 DhtProxyServer::sendPushNotification(const std::string& token, Json::Value&& json, PushType type, bool highPriority, const std::string& topic)
 {
-    if (pushServer_.empty())
+    if (pushServer_.empty() and unifiedPushEndpoint_.empty())
         return;
 
     unsigned reqid = 0;
     try {
-        auto request = std::make_shared<http::Request>(io_context(), pushHostPort_.first, pushHostPort_.second,
-                                                            pushHostPort_.first.find("https://") == 0, logger_);
+        auto request = type == PushType::UnifiedPush
+            ? std::make_shared<http::Request>(io_context(), unifiedPushEndpoint_, logger_)
+            : std::make_shared<http::Request>(io_context(), pushHostPort_.first, pushHostPort_.second, pushHostPort_.first.find("https://") == 0, logger_);
         reqid = request->id();
-        request->set_target("/api/push");
+        request->set_target(type == PushType::UnifiedPush ? ("/" + token) : "/api/push");
         request->set_method(restinio::http_method_post());
-        request->set_header_field(restinio::http_field_t::host, pushServer_.c_str());
+        request->set_header_field(restinio::http_field_t::host, type == PushType::UnifiedPush ? unifiedPushEndpoint_.c_str() : pushServer_.c_str());
         request->set_header_field(restinio::http_field_t::user_agent, "RESTinio client");
         request->set_header_field(restinio::http_field_t::accept, "*/*");
         request->set_header_field(restinio::http_field_t::content_type, "application/json");
 
-        // NOTE: see https://github.com/appleboy/gorush
-        Json::Value notification(Json::objectValue);
-        Json::Value tokens(Json::arrayValue);
-        tokens[0] = token;
-        notification["tokens"] = std::move(tokens);
-        notification["platform"] = type == PushType::Android ? 2 : 1;
-        notification["data"] = std::move(json);
-        notification["priority"] = highPriority ? "high" : "normal";
-        if (type == PushType::Android)
-            notification["time_to_live"] = 3600 * 24; // 24 hours
-        else {
-            const auto expiration = std::chrono::system_clock::now() + std::chrono::hours(24);
-            uint32_t exp = std::chrono::duration_cast<std::chrono::seconds>(expiration.time_since_epoch()).count();
-            notification["expiration"] = exp;
-            if (!topic.empty())
-                notification["topic"] = topic;
-            if (highPriority) {
-                Json::Value alert(Json::objectValue);
-                alert["title"]="hello";
-                notification["push_type"] = "alert";
-                notification["alert"] = alert;
-                notification["mutable_content"] = true;
-            } else {
-                notification["push_type"] = "background";
-                notification["content_available"] = true;
+        if (type == PushType::UnifiedPush) {
+            Json::Value notification(Json::objectValue);
+            notification["message"] = Json::writeString(jsonBuilder_, std::move(json));
+            notification["topic"] = token;
+            notification["priority"] = highPriority ? 5 : 1;
+            request->set_body(Json::writeString(jsonBuilder_, std::move(json)));
+        } else {
+            // NOTE: see https://github.com/appleboy/gorush
+            Json::Value notification(Json::objectValue);
+            Json::Value tokens(Json::arrayValue);
+            tokens[0] = token;
+            notification["tokens"] = std::move(tokens);
+            notification["platform"] = type == PushType::Android ? 2 : 1;
+            notification["data"] = std::move(json);
+            notification["priority"] = highPriority ? "high" : "normal";
+            if (type == PushType::Android)
+                notification["time_to_live"] = 3600 * 24; // 24 hours
+            else {
+                const auto expiration = std::chrono::system_clock::now() + std::chrono::hours(24);
+                uint32_t exp = std::chrono::duration_cast<std::chrono::seconds>(expiration.time_since_epoch()).count();
+                notification["expiration"] = exp;
+                if (!topic.empty())
+                    notification["topic"] = topic;
+                if (highPriority) {
+                    Json::Value alert(Json::objectValue);
+                    alert["title"]="hello";
+                    notification["push_type"] = "alert";
+                    notification["alert"] = alert;
+                    notification["mutable_content"] = true;
+                } else {
+                    notification["push_type"] = "background";
+                    notification["content_available"] = true;
+                }
             }
-        }
 
-        Json::Value notifications(Json::arrayValue);
-        notifications[0] = notification;
+            Json::Value notifications(Json::arrayValue);
+            notifications[0] = notification;
+
+            Json::Value content;
+            content["notifications"] = std::move(notifications);
+            request->set_body(Json::writeString(jsonBuilder_, content));
+        }
 
-        Json::Value content;
-        content["notifications"] = std::move(notifications);
-        request->set_body(Json::writeString(jsonBuilder_, content));
         request->add_on_state_change_callback([this, reqid]
                                               (http::Request::State state, const http::Response& response){
             if (state == http::Request::State::DONE){
diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index 5767641ea7da68845f830b258edd91ade98a4dd3..1c8d20a4b0d8479393be637574feebc543a5a734 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -43,7 +43,7 @@ void print_version() {
 }
 
 void print_usage() {
-    std::cout << "Usage: dhtnode [-v [-l logfile]] [-i] [-d] [-n network_id] [-p local_port] [-b bootstrap_host[:port]] [--proxyserver local_port] [--proxyserverssl local_port] [--bundleid bundleid]" << std::endl << std::endl;
+    std::cout << "Usage: dhtnode [-v [-l logfile]] [-i] [-d] [-n network_id] [-p local_port] [-b bootstrap_host[:port]] [--proxyserver local_port] [--proxyserverssl local_port] [--bundleid bundleid] [--openpush endpoint]" << std::endl << std::endl;
     print_info();
 }
 
@@ -552,6 +552,7 @@ main(int argc, char **argv)
             serverConfig.pushServer = params.pushserver;
             serverConfig.bundleId = params.bundle_id;
             serverConfig.address = params.proxy_address;
+            serverConfig.unifiedPushEndpoint = params.unifiedPushEndpoint;
             if (params.proxyserverssl and params.proxy_id.first and params.proxy_id.second){
                 serverConfig.identity = params.proxy_id;
                 serverConfig.port = params.proxyserverssl;
diff --git a/tools/tools_common.h b/tools/tools_common.h
index 258482ab49e2b253fe16cd211bb510ba5d4a7ff7..bb783d93946a7c1344f62d07e4beb4e772d16052 100644
--- a/tools/tools_common.h
+++ b/tools/tools_common.h
@@ -130,6 +130,7 @@ struct dht_params {
     in_port_t port {0};
     in_port_t proxyserver {0};
     in_port_t proxyserverssl {0};
+    std::string unifiedPushEndpoint {};
     std::string proxyclient {};
     std::string proxy_address {};
     std::string pushserver {};
@@ -225,6 +226,7 @@ static const constexpr struct option long_options[] = {
     {"syslog",                  no_argument      , nullptr, 'L'},
     {"proxyserver",             required_argument, nullptr, 'S'},
     {"proxyserverssl",          required_argument, nullptr, 'e'},
+    {"unifiedpush",             required_argument, nullptr, 'O'},
     {"proxy-addr",              required_argument, nullptr, 'a'},
     {"proxy-certificate",       required_argument, nullptr, 'w'},
     {"proxy-privkey",           required_argument, nullptr, 'K'},
@@ -243,7 +245,7 @@ parseArgs(int argc, char **argv) {
     int opt;
     std::string privkey;
     std::string proxy_privkey;
-    while ((opt = getopt_long(argc, argv, "hidsvDUPp:n:b:f:l:", long_options, nullptr)) != -1) {
+    while ((opt = getopt_long(argc, argv, "hidsvODUPp:n:b:f:l:", long_options, nullptr)) != -1) {
         switch (opt) {
         case 'p': {
                 int port_arg = atoi(optarg);
@@ -261,6 +263,9 @@ parseArgs(int argc, char **argv) {
                     std::cout << "Invalid port: " << port_arg << std::endl;
             }
             break;
+        case 'O':
+            params.unifiedPushEndpoint = optarg;
+            break;
         case 'e': {
                 int port_arg = atoi(optarg);
                 if (port_arg >= 0 && port_arg < 0x10000)