diff --git a/api/funkwhale_api/federation/actors.py b/api/funkwhale_api/federation/actors.py
index d70ce23e5fab8eb27af8bf568f2e331a639f9189..89c621edcae49d0552b5df74ed72f11c89c266aa 100644
--- a/api/funkwhale_api/federation/actors.py
+++ b/api/funkwhale_api/federation/actors.py
@@ -130,7 +130,7 @@ class SystemActor(object):
                 'No handler for activity %s', ac['type'])
             return
 
-        return handler(ac, actor)
+        return handler(data, actor)
 
 
 class LibraryActor(SystemActor):
@@ -269,6 +269,40 @@ class TestActor(SystemActor):
             to=[ac['actor']],
             on_behalf_of=test_actor)
 
+    def handle_undo(self, ac, sender):
+        if ac['object']['type'] != 'Follow':
+            return
+
+        if ac['object']['actor'] != sender.url:
+            # not the same actor, permission issue
+            return
+
+        test_actor = self.get_actor_instance()
+        models.Follow.objects.filter(
+            actor=sender,
+            target=test_actor,
+        ).delete()
+        # we also unfollow the sender, if possible
+        try:
+            follow = models.Follow.objects.get(
+                target=sender,
+                actor=test_actor,
+            )
+        except models.Follow.DoesNotExist:
+            return
+        undo = {
+            '@context': serializers.AP_CONTEXT,
+            'type': 'Undo',
+            'id': follow.get_federation_url() + '/undo',
+            'actor': test_actor.url,
+            'object': serializers.FollowSerializer(follow).data,
+        }
+        follow.delete()
+        activity.deliver(
+            undo,
+            to=[sender.url],
+            on_behalf_of=test_actor)
+
 SYSTEM_ACTORS = {
     'library': LibraryActor(),
     'test': TestActor(),
diff --git a/api/funkwhale_api/federation/factories.py b/api/funkwhale_api/federation/factories.py
index 217b472187eb86ecf55722ca2b69a85eebde63aa..16abf80dc921347a0041148c3f7b73c46e443d4b 100644
--- a/api/funkwhale_api/federation/factories.py
+++ b/api/funkwhale_api/federation/factories.py
@@ -3,6 +3,7 @@ import requests
 import requests_http_signature
 
 from django.utils import timezone
+from django.conf import settings
 
 from funkwhale_api.factories import registry
 
@@ -65,6 +66,12 @@ class ActorFactory(factory.DjangoModelFactory):
     class Meta:
         model = models.Actor
 
+    class Params:
+        local = factory.Trait(
+            domain=factory.LazyAttribute(
+                lambda o: settings.FEDERATION_HOSTNAME)
+        )
+
     @classmethod
     def _generate(cls, create, attrs):
         has_public = attrs.get('public_key') is not None
@@ -84,6 +91,11 @@ class FollowFactory(factory.DjangoModelFactory):
     class Meta:
         model = models.Follow
 
+    class Params:
+        local = factory.Trait(
+            actor=factory.SubFactory(ActorFactory, local=True)
+        )
+
 
 @registry.register(name='federation.Note')
 class NoteFactory(factory.Factory):
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index 875268bca3ff999bc7ced6c43c0b16bafb47dece..a228a38033832254f2c96d6154447376a9482e3d 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -14,6 +14,8 @@ TYPE_CHOICES = [
 
 
 class Actor(models.Model):
+    ap_type = 'Actor'
+
     url = models.URLField(unique=True, max_length=500, db_index=True)
     outbox_url = models.URLField(max_length=500)
     inbox_url = models.URLField(max_length=500)
@@ -79,6 +81,8 @@ class Actor(models.Model):
 
 
 class Follow(models.Model):
+    ap_type = 'Follow'
+
     uuid = models.UUIDField(default=uuid.uuid4, unique=True)
     actor = models.ForeignKey(
         Actor,
@@ -96,3 +100,6 @@ class Follow(models.Model):
 
     class Meta:
         unique_together = ['actor', 'target']
+
+    def get_federation_url(self):
+        return '{}#follows/{}'.format(self.actor.url, self.uuid)
diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py
index d50b52ee681473049d6c730a440e2156872fa45d..c1b9d8a235ce3ca6011d21a286df71208d0b58cb 100644
--- a/api/tests/federation/test_actors.py
+++ b/api/tests/federation/test_actors.py
@@ -169,11 +169,7 @@ def test_test_post_inbox_handles_create_note(
         }]
     )
     expected_activity = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
-            {}
-        ],
+        '@context': serializers.AP_CONTEXT,
         'actor': test_actor.url,
         'id': 'https://{}/activities/note/{}/activity'.format(
             settings.FEDERATION_HOSTNAME, now.timestamp()
@@ -288,11 +284,7 @@ def test_test_actor_handles_follow(
     	},
     }
     expected_follow = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
-            {}
-        ],
+        '@context': serializers.AP_CONTEXT,
         'actor': test_actor.url,
         'id': test_actor.url + '#follows/{}'.format(uid),
         'object': actor.url,
@@ -317,3 +309,38 @@ def test_test_actor_handles_follow(
     follow = test_actor.received_follows.first()
     assert follow.actor == actor
     assert follow.target == test_actor
+
+
+def test_test_actor_handles_undo_follow(
+        settings, mocker, factories):
+    deliver = mocker.patch(
+        'funkwhale_api.federation.activity.deliver')
+    test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance()
+    follow = factories['federation.Follow'](target=test_actor)
+    reverse_follow = factories['federation.Follow'](
+        actor=test_actor, target=follow.actor)
+    follow_serializer = serializers.FollowSerializer(follow)
+    reverse_follow_serializer = serializers.FollowSerializer(
+        reverse_follow)
+    undo = {
+        '@context': serializers.AP_CONTEXT,
+        'type': 'Undo',
+        'id': follow_serializer.data['id'] + '/undo',
+        'actor': follow.actor.url,
+        'object': follow_serializer.data,
+    }
+    expected_undo = {
+        '@context': serializers.AP_CONTEXT,
+        'type': 'Undo',
+        'id': reverse_follow_serializer.data['id'] + '/undo',
+        'actor': reverse_follow.actor.url,
+        'object': reverse_follow_serializer.data,
+    }
+
+    actors.SYSTEM_ACTORS['test'].post_inbox(undo, actor=follow.actor)
+    deliver.assert_called_once_with(
+        expected_undo,
+        to=[follow.actor.url],
+        on_behalf_of=test_actor,)
+
+    assert models.Follow.objects.count() == 0
diff --git a/api/tests/federation/test_authentication.py b/api/tests/federation/test_authentication.py
index 1837b3950f471ba549d8f45077d5b773cc010da3..c6a97a07a75fc52065f4a7cc7a1a710015788a86 100644
--- a/api/tests/federation/test_authentication.py
+++ b/api/tests/federation/test_authentication.py
@@ -3,7 +3,7 @@ from funkwhale_api.federation import keys
 from funkwhale_api.federation import signing
 
 
-def test_authenticate(nodb_factories, mocker, api_request):
+def test_authenticate(factories, mocker, api_request):
     private, public = keys.get_key_pair()
     actor_url = 'https://test.federation/actor'
     mocker.patch(
@@ -18,7 +18,7 @@ def test_authenticate(nodb_factories, mocker, api_request):
                 'id': actor_url + '#main-key',
             }
         })
-    signed_request = nodb_factories['federation.SignedRequest'](
+    signed_request = factories['federation.SignedRequest'](
         auth__key=private,
         auth__key_id=actor_url + '#main-key',
         auth__headers=[
diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py
index 297fe2c58200c0c001430359daf5e0e908f332d5..18daf87887f94abb12a43a35a06ef3aabd7485d6 100644
--- a/api/tests/federation/test_models.py
+++ b/api/tests/federation/test_models.py
@@ -23,3 +23,10 @@ def test_cannot_duplicate_follow(factories):
             target=follow.target,
             actor=follow.actor,
         )
+
+def test_follow_federation_url(factories):
+    follow = factories['federation.Follow'](local=True)
+    expected = '{}#follows/{}'.format(
+        follow.actor.url, follow.uuid)
+
+    assert follow.get_federation_url() == expected