diff --git a/bin/nodejs/callback.h b/bin/nodejs/callback.h
index 4ec2e5923288606e1ddb1bd3a153798eecf50502..e0b05847879692a4f9170a5dd7e9edc551b37c51 100644
--- a/bin/nodejs/callback.h
+++ b/bin/nodejs/callback.h
@@ -35,6 +35,9 @@ Persistent<Function> swarmLoadedCb;
 Persistent<Function> messagesFoundCb;
 Persistent<Function> messageReceivedCb;
 Persistent<Function> swarmMessageReceivedCb;
+Persistent<Function> swarmMessageUpdatedCb;
+Persistent<Function> reactionAddedCb;
+Persistent<Function> reactionRemovedCb;
 Persistent<Function> conversationProfileUpdatedCb;
 Persistent<Function> conversationRequestReceivedCb;
 Persistent<Function> conversationRequestDeclinedCb;
@@ -109,6 +112,12 @@ getPresistentCb(std::string_view signal)
         return &messageReceivedCb;
     else if (signal == "SwarmMessageReceived")
         return &swarmMessageReceivedCb;
+    else if (signal == "SwarmMessageUpdated")
+        return &swarmMessageUpdatedCb;
+    else if (signal == "ReactionAdded")
+        return &reactionAddedCb;
+    else if (signal == "ReactionRemoved")
+        return &reactionRemovedCb;
     else if (signal == "ConversationProfileUpdated")
         return &conversationProfileUpdatedCb;
     else if (signal == "ConversationReady")
@@ -139,6 +148,9 @@ getPresistentCb(std::string_view signal)
         return nullptr;
 }
 
+#define V8_STRING_LITERAL(str) \
+    v8::String::NewFromUtf8Literal(v8::Isolate::GetCurrent(), str)
+
 #define V8_STRING_NEW(str) \
     v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), \
                             str.data(), \
@@ -174,6 +186,17 @@ stringMapToJsMap(const std::map<std::string, std::string>& strmap)
     return jsMap;
 }
 
+inline SWIGV8_OBJECT
+stringIntMapToJsMap(const std::map<std::string, int32_t>& strmap)
+{
+    SWIGV8_OBJECT jsMap = SWIGV8_OBJECT_NEW();
+    for (auto& kvpair : strmap)
+        jsMap->Set(SWIGV8_CURRENT_CONTEXT(),
+                   V8_STRING_NEW_LOCAL(std::get<0>(kvpair)),
+                   SWIGV8_INTEGER_NEW(std::get<1>(kvpair)));
+    return jsMap;
+}
+
 inline SWIGV8_ARRAY
 stringMapVecToJsMapArray(const std::vector<std::map<std::string, std::string>>& vect)
 {
@@ -183,6 +206,29 @@ stringMapVecToJsMapArray(const std::vector<std::map<std::string, std::string>>&
     return jsArray;
 }
 
+inline SWIGV8_OBJECT
+swarmMessageToJs(const libjami::SwarmMessage& message)
+{
+    SWIGV8_OBJECT jsMap = SWIGV8_OBJECT_NEW();
+    jsMap->Set(SWIGV8_CURRENT_CONTEXT(), V8_STRING_LITERAL("id"), V8_STRING_NEW_LOCAL(message.id));
+    jsMap->Set(SWIGV8_CURRENT_CONTEXT(), V8_STRING_LITERAL("type"), V8_STRING_NEW_LOCAL(message.type));
+    jsMap->Set(SWIGV8_CURRENT_CONTEXT(), V8_STRING_LITERAL("linearizedParent"), V8_STRING_NEW_LOCAL(message.linearizedParent));
+    jsMap->Set(SWIGV8_CURRENT_CONTEXT(), V8_STRING_LITERAL("body"), stringMapToJsMap(message.body));
+    jsMap->Set(SWIGV8_CURRENT_CONTEXT(), V8_STRING_LITERAL("reactions"), stringMapVecToJsMapArray(message.reactions));
+    jsMap->Set(SWIGV8_CURRENT_CONTEXT(), V8_STRING_LITERAL("editions"), stringMapVecToJsMapArray(message.editions));
+    jsMap->Set(SWIGV8_CURRENT_CONTEXT(), V8_STRING_LITERAL("status"), stringIntMapToJsMap(message.status));
+    return jsMap;
+}
+
+inline SWIGV8_ARRAY
+swarmMessagesToJsArray(const std::vector<libjami::SwarmMessage>& messages)
+{
+    SWIGV8_ARRAY jsArray = SWIGV8_ARRAY_NEW(messages.size());
+    for (unsigned int i = 0; i < messages.size(); i++)
+        jsArray->Set(SWIGV8_CURRENT_CONTEXT(), SWIGV8_INTEGER_NEW_UNS(i), swarmMessageToJs(messages[i]));
+    return jsArray;
+}
+
 void
 setCallback(std::string_view signal, Local<Function>& func)
 {
@@ -643,6 +689,102 @@ conversationLoaded(uint32_t id,
     uv_async_send(&signalAsync);
 }
 
+void
+swarmLoaded(uint32_t id,
+              const std::string& accountId,
+              const std::string& conversationId,
+              const std::vector<libjami::SwarmMessage>& messages)
+{
+    std::lock_guard lock(pendingSignalsLock);
+    pendingSignals.emplace([id, accountId, conversationId, messages]() {
+        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), swarmLoadedCb);
+        if (!func.IsEmpty()) {
+            SWIGV8_VALUE callback_args[] = {SWIGV8_INTEGER_NEW_UNS(id),
+                                            V8_STRING_NEW_LOCAL(accountId),
+                                            V8_STRING_NEW_LOCAL(conversationId),
+                                            swarmMessagesToJsArray(messages)};
+            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 4, callback_args);
+        }
+    });
+    uv_async_send(&signalAsync);
+}
+
+void
+swarmMessageReceived(const std::string& accountId,
+                     const std::string& conversationId,
+                     const libjami::SwarmMessage& message)
+{
+    std::lock_guard lock(pendingSignalsLock);
+    pendingSignals.emplace([accountId, conversationId, message]() {
+        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), swarmMessageReceivedCb);
+        if (!func.IsEmpty()) {
+            SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId),
+                                            V8_STRING_NEW_LOCAL(conversationId),
+                                            swarmMessageToJs(message)};
+            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 3, callback_args);
+        }
+    });
+    uv_async_send(&signalAsync);
+}
+
+void
+swarmMessageUpdated(const std::string& accountId,
+                    const std::string& conversationId,
+                    const libjami::SwarmMessage& message)
+{
+    std::lock_guard lock(pendingSignalsLock);
+    pendingSignals.emplace([accountId, conversationId, message]() {
+        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), swarmMessageUpdatedCb);
+        if (!func.IsEmpty()) {
+            SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId),
+                                            V8_STRING_NEW_LOCAL(conversationId),
+                                            swarmMessageToJs(message)};
+            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 3, callback_args);
+        }
+    });
+    uv_async_send(&signalAsync);
+}
+
+void
+reactionAdded(const std::string& accountId,
+              const std::string& conversationId,
+              const std::string& messageId,
+              const std::map<std::string, std::string>& reaction)
+{
+    std::lock_guard lock(pendingSignalsLock);
+    pendingSignals.emplace([accountId, conversationId, messageId, reaction]() {
+        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), reactionAddedCb);
+        if (!func.IsEmpty()) {
+            SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId),
+                                            V8_STRING_NEW_LOCAL(conversationId),
+                                            V8_STRING_NEW_LOCAL(messageId),
+                                            stringMapToJsMap(reaction)};
+            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 4, callback_args);
+        }
+    });
+    uv_async_send(&signalAsync);
+}
+
+void
+reactionRemoved(const std::string& accountId,
+                const std::string& conversationId,
+                const std::string& messageId,
+                const std::string& reactionId)
+{
+    std::lock_guard lock(pendingSignalsLock);
+    pendingSignals.emplace([accountId, conversationId, messageId, reactionId]() {
+        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), reactionRemovedCb);
+        if (!func.IsEmpty()) {
+            SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId),
+                                            V8_STRING_NEW_LOCAL(conversationId),
+                                            V8_STRING_NEW_LOCAL(messageId),
+                                            V8_STRING_NEW_LOCAL(reactionId)};
+            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 4, callback_args);
+        }
+    });
+    uv_async_send(&signalAsync);
+}
+
 void
 messagesFound(uint32_t id,
               const std::string& accountId,