diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py
index c94b08d0022ef2ac43f2320c94e1f0765fe10dd3..e253906de71c77020b483b9af6d3cd9b974dd3e8 100644
--- a/api/funkwhale_api/common/serializers.py
+++ b/api/funkwhale_api/common/serializers.py
@@ -159,3 +159,34 @@ class ActionSerializer(serializers.Serializer):
             "result": result,
         }
         return payload
+
+
+def track_fields_for_update(*fields):
+    """
+    Apply this decorator to serializer to call function when specific values
+    are updated on an object:
+
+    .. code-block:: python
+
+        @track_fields_for_update('privacy_level')
+        class LibrarySerializer(serializers.ModelSerializer):
+            def on_updated_privacy_level(self, obj, old_value, new_value):
+                print('Do someting')
+    """
+
+    def decorator(serializer_class):
+        original_update = serializer_class.update
+
+        def new_update(self, obj, validated_data):
+            tracked_fields_before = {f: getattr(obj, f) for f in fields}
+            obj = original_update(self, obj, validated_data)
+            tracked_fields_after = {f: getattr(obj, f) for f in fields}
+
+            if tracked_fields_before != tracked_fields_after:
+                self.on_updated_fields(obj, tracked_fields_before, tracked_fields_after)
+            return obj
+
+        serializer_class.update = new_update
+        return serializer_class
+
+    return decorator
diff --git a/api/funkwhale_api/federation/routes.py b/api/funkwhale_api/federation/routes.py
index d8877de1fd1c7bbcf81859d27ed6a9725895cc06..0295aa46ce4004648075371be181b64072c773e9 100644
--- a/api/funkwhale_api/federation/routes.py
+++ b/api/funkwhale_api/federation/routes.py
@@ -195,6 +195,45 @@ def outbox_delete_library(context):
     }
 
 
+@outbox.register({"type": "Update", "object.type": "Library"})
+def outbox_update_library(context):
+    library = context["library"]
+    serializer = serializers.ActivitySerializer(
+        {"type": "Update", "object": serializers.LibrarySerializer(library).data}
+    )
+
+    yield {
+        "type": "Update",
+        "actor": library.actor,
+        "payload": with_recipients(
+            serializer.data, to=[{"type": "followers", "target": library}]
+        ),
+    }
+
+
+@inbox.register({"type": "Update", "object.type": "Library"})
+def inbox_update_library(payload, context):
+    actor = context["actor"]
+    library_id = payload["object"].get("id")
+    if not library_id:
+        logger.debug("Discarding deletion of empty library")
+        return
+
+    if not actor.libraries.filter(fid=library_id).exists():
+        logger.debug("Discarding deletion of unkwnown library %s", library_id)
+        return
+
+    serializer = serializers.LibrarySerializer(data=payload["object"])
+    if serializer.is_valid():
+        serializer.save()
+    else:
+        logger.debug(
+            "Discarding update of library %s because of payload errors: %s",
+            library_id,
+            serializer.errors,
+        )
+
+
 @inbox.register({"type": "Delete", "object.type": "Audio"})
 def inbox_delete_audio(payload, context):
     actor = context["actor"]
diff --git a/api/funkwhale_api/federation/signing.py b/api/funkwhale_api/federation/signing.py
index 1bb2b9d5de04fc54e5557a6097b2f3215cd73efe..5b7a9b8d4f67e65d5d027d0b58413fd68a94b48b 100644
--- a/api/funkwhale_api/federation/signing.py
+++ b/api/funkwhale_api/federation/signing.py
@@ -85,7 +85,7 @@ def verify_django(django_request, public_key):
 def get_auth(private_key, private_key_id):
     return requests_http_signature.HTTPSignatureAuth(
         use_auth_header=False,
-        headers=["(request-target)", "user-agent", "host", "date", "content-type"],
+        headers=["(request-target)", "user-agent", "host", "date"],
         algorithm="rsa-sha256",
         key=private_key.encode("utf-8"),
         key_id=private_key_id,
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index 5d1dab00696fef6614c73371a4419c42cfbf2b6e..12f41f2d7acb404dfc673558629f214d68ef3149 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -192,6 +192,7 @@ class TrackSerializer(serializers.ModelSerializer):
         return TrackUploadSerializer(uploads, many=True).data
 
 
+@common_serializers.track_fields_for_update("name", "description", "privacy_level")
 class LibraryForOwnerSerializer(serializers.ModelSerializer):
     uploads_count = serializers.SerializerMethodField()
     size = serializers.SerializerMethodField()
@@ -216,6 +217,11 @@ class LibraryForOwnerSerializer(serializers.ModelSerializer):
     def get_size(self, o):
         return getattr(o, "_size", 0)
 
+    def on_updated_fields(self, obj, before, after):
+        routes.outbox.dispatch(
+            {"type": "Update", "object": {"type": "Library"}}, context={"library": obj}
+        )
+
 
 class UploadSerializer(serializers.ModelSerializer):
     track = TrackSerializer(required=False, allow_null=True)
diff --git a/api/tests/common/test_serializers.py b/api/tests/common/test_serializers.py
index e07bf8e826bfec8ebe77de5be57b1b7ce9f2d553..bf04e8d2ae015533dadabbf88e96ac1ed6cdf2b2 100644
--- a/api/tests/common/test_serializers.py
+++ b/api/tests/common/test_serializers.py
@@ -134,3 +134,32 @@ def test_action_serializers_can_require_filter(factories):
 
     assert serializer.is_valid(raise_exception=True) is True
     assert list(serializer.validated_data["objects"]) == [user1]
+
+
+def test_track_fields_for_update(mocker):
+    @serializers.track_fields_for_update("field1", "field2")
+    class S(serializers.serializers.Serializer):
+        field1 = serializers.serializers.CharField()
+        field2 = serializers.serializers.CharField()
+
+        def update(self, obj, validated_data):
+            for key, value in validated_data.items():
+                setattr(obj, key, value)
+            return obj
+
+        on_updated_fields = mocker.stub()
+
+    class Obj(object):
+        field1 = "value1"
+        field2 = "value2"
+
+    obj = Obj()
+    serializer = S(obj, data={"field1": "newvalue1", "field2": "newvalue2"})
+    assert serializer.is_valid(raise_exception=True)
+    serializer.save()
+
+    serializer.on_updated_fields.assert_called_once_with(
+        obj,
+        {"field1": "value1", "field2": "value2"},
+        {"field1": "newvalue1", "field2": "newvalue2"},
+    )
diff --git a/api/tests/federation/test_routes.py b/api/tests/federation/test_routes.py
index 79d194f5636c6ea396174946428d1953c7e8dbbf..7232b746cfa0768992ced9ca0bec818c58246bfa 100644
--- a/api/tests/federation/test_routes.py
+++ b/api/tests/federation/test_routes.py
@@ -9,6 +9,7 @@ from funkwhale_api.federation import routes, serializers
         ({"type": "Follow"}, routes.inbox_follow),
         ({"type": "Accept"}, routes.inbox_accept),
         ({"type": "Create", "object.type": "Audio"}, routes.inbox_create_audio),
+        ({"type": "Update", "object.type": "Library"}, routes.inbox_update_library),
         ({"type": "Delete", "object.type": "Library"}, routes.inbox_delete_library),
         ({"type": "Delete", "object.type": "Audio"}, routes.inbox_delete_audio),
         ({"type": "Undo", "object.type": "Follow"}, routes.inbox_undo_follow),
@@ -29,6 +30,7 @@ def test_inbox_routes(route, handler):
         ({"type": "Accept"}, routes.outbox_accept),
         ({"type": "Follow"}, routes.outbox_follow),
         ({"type": "Create", "object.type": "Audio"}, routes.outbox_create_audio),
+        ({"type": "Update", "object.type": "Library"}, routes.outbox_update_library),
         ({"type": "Delete", "object.type": "Library"}, routes.outbox_delete_library),
         ({"type": "Delete", "object.type": "Audio"}, routes.outbox_delete_audio),
         ({"type": "Undo", "object.type": "Follow"}, routes.outbox_undo_follow),
@@ -262,6 +264,55 @@ def test_outbox_delete_library(factories):
     assert activity["actor"] == library.actor
 
 
+def test_outbox_update_library(factories):
+    library = factories["music.Library"]()
+    activity = list(routes.outbox_update_library({"library": library}))[0]
+    expected = serializers.ActivitySerializer(
+        {"type": "Update", "object": serializers.LibrarySerializer(library).data}
+    ).data
+
+    expected["to"] = [{"type": "followers", "target": library}]
+
+    assert dict(activity["payload"]) == dict(expected)
+    assert activity["actor"] == library.actor
+
+
+def test_inbox_update_library(factories):
+    activity = factories["federation.Activity"]()
+
+    library = factories["music.Library"]()
+    data = serializers.LibrarySerializer(library).data
+    data["name"] = "New name"
+    payload = {"type": "Update", "actor": library.actor.fid, "object": data}
+
+    routes.inbox_update_library(
+        payload,
+        context={"actor": library.actor, "raise_exception": True, "activity": activity},
+    )
+
+    library.refresh_from_db()
+    assert library.name == "New name"
+
+
+# def test_inbox_update_library_impostor(factories):
+#     activity = factories["federation.Activity"]()
+#     impostor = factories["federation.Actor"]()
+#     library = factories["music.Library"]()
+#     payload = {
+#         "type": "Delete",
+#         "actor": library.actor.fid,
+#         "object": {"type": "Library", "id": library.fid},
+#     }
+
+#     routes.inbox_update_library(
+#         payload,
+#         context={"actor": impostor, "raise_exception": True, "activity": activity},
+#     )
+
+#     # not deleted, should still be here
+#     library.refresh_from_db()
+
+
 def test_inbox_delete_audio(factories):
     activity = factories["federation.Activity"]()
 
diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py
index 3bd13a599150ee8a32af3e00932bd58454853df9..92b924486c5114c574994df400bc1e7af2d5a0fd 100644
--- a/api/tests/music/test_serializers.py
+++ b/api/tests/music/test_serializers.py
@@ -1,3 +1,5 @@
+import pytest
+
 from funkwhale_api.music import models
 from funkwhale_api.music import serializers
 from funkwhale_api.music import tasks
@@ -274,3 +276,28 @@ def test_track_upload_serializer(factories):
 
     serializer = serializers.TrackUploadSerializer(upload)
     assert serializer.data == expected
+
+
+@pytest.mark.parametrize(
+    "field,before,after",
+    [
+        ("privacy_level", "me", "everyone"),
+        ("name", "Before", "After"),
+        ("description", "Before", "After"),
+    ],
+)
+def test_update_library_privacy_level_broadcasts_to_followers(
+    factories, field, before, after, mocker
+):
+    dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
+    library = factories["music.Library"](**{field: before})
+
+    serializer = serializers.LibraryForOwnerSerializer(
+        library, data={field: after}, partial=True
+    )
+    assert serializer.is_valid(raise_exception=True)
+    serializer.save()
+
+    dispatch.assert_called_once_with(
+        {"type": "Update", "object": {"type": "Library"}}, context={"library": library}
+    )
diff --git a/changes/changelog.d/library-update-federation.enhancement b/changes/changelog.d/library-update-federation.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..4c777ad99eb724487941fde097b34df0383d8c1d
--- /dev/null
+++ b/changes/changelog.d/library-update-federation.enhancement
@@ -0,0 +1 @@
+Broadcast library updates (name, description, visibility) over federation