diff --git a/djangoldp/activities/verbs.py b/djangoldp/activities/verbs.py
index 65bfe09f0db269f77f1bf6008fa3f4f68d182e11..313b738f799021162e7f1208e78b4f886af6c404 100644
--- a/djangoldp/activities/verbs.py
+++ b/djangoldp/activities/verbs.py
@@ -1,5 +1,6 @@
 from copy import copy
 
+from django.conf import settings
 from djangoldp.activities import errors
 from djangoldp.activities.objects import ALLOWED_TYPES, Object, Actor
 
@@ -7,6 +8,11 @@ from djangoldp.activities.objects import ALLOWED_TYPES, Object, Actor
 class Activity(Object):
     attributes = Object.attributes + ["actor", "object"]
     type = "Activity"
+    # dictionary defining required attributes -> tuple of acceptable types
+    required_attributes = {
+        "actor": (Actor, str),
+        "object": dict
+    }
 
     def get_audience(self):
         audience = []
@@ -29,30 +35,16 @@ class Activity(Object):
         return new
 
     def validate(self):
-        pass
+        for attr in self.required_attributes.keys():
+            if not isinstance(getattr(self, attr, None), self.required_attributes[attr]):
+                raise errors.ActivityStreamValidationError("required attribute " + attr + " of type "
+                                                           + str(self.required_attributes[attr]))
 
 
 class Add(Activity):
     type = "Add"
     attributes = Activity.attributes + ["target"]
-
-    def validate(self):
-        msg = None
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not getattr(self, "target", None):
-            msg = "Invalid activity, target is missing"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
-        elif not isinstance(self.target, dict):
-            msg = "Invalid target type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
+    required_attributes = {**Activity.required_attributes, "target": dict}
 
 
 class Remove(Activity):
@@ -60,49 +52,24 @@ class Remove(Activity):
     attributes = Activity.attributes + ["target", "origin"]
 
     def validate(self):
-        msg = None
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not getattr(self, "target", None) and not getattr(self, "origin", None):
-            msg = "Invalid activity, no target or origin given"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
+        super().validate()
+
+        if not getattr(self, "target", None) and not getattr(self, "origin", None):
+            raise errors.ActivityStreamValidationError("Invalid activity, no target or origin given")
 
         if getattr(self, "target", None) is not None:
             if not isinstance(self.target, dict):
-                msg = "Invalid target type, must be a dict"
+                raise errors.ActivityStreamValidationError("Invalid target type, must be a dict")
         if getattr(self, "origin", None) is not None:
             if not isinstance(self.origin, dict):
-                msg = "Invalid origin type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
+                raise errors.ActivityStreamValidationError("Invalid origin type, must be a dict")
 
 
 class Create(Activity):
     type = "Create"
 
-    def validate(self):
-        msg = None
-
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
 
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
-
-
-class Update(Create):
+class Update(Activity):
     type = "Update"
 
 
@@ -110,40 +77,15 @@ class Delete(Activity):
     type = "Delete"
     attributes = Activity.attributes + ["origin"]
 
-    def validate(self):
-        msg = None
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
-
 
 class Follow(Activity):
     type = "Follow"
 
     def validate(self):
-        msg = None
-
-        if not getattr(self, "actor", None):
-            msg = "Invalid activity, actor is missing"
-        elif not getattr(self, "object", None):
-            msg = "Invalid activity, object is missing"
-        elif not isinstance(self.actor, Actor) and not isinstance(self.actor, str):
-            msg = "Invalid actor type, must be an Actor or a string"
-        elif isinstance(self.actor, Actor) and (self.actor.inbox is None and self.actor.id is None):
-            msg = "Must pass inbox or id with the actor to follow"
-        elif not isinstance(self.object, dict):
-            msg = "Invalid object type, must be a dict"
-
-        if msg:
-            raise errors.ActivityStreamValidationError(msg)
+        super().validate()
+
+        if isinstance(self.actor, Actor) and (self.actor.inbox is None and self.actor.id is None):
+            raise errors.ActivityStreamValidationError("Must pass inbox or id with the actor to follow")
 
 
 ALLOWED_TYPES.update({
diff --git a/djangoldp/tests/tests_inbox.py b/djangoldp/tests/tests_inbox.py
index d29a18e39d218a537e463eee2ff8729b71764512..21df3c5eaac3e0c38a61bc6602767c02ecceac97 100644
--- a/djangoldp/tests/tests_inbox.py
+++ b/djangoldp/tests/tests_inbox.py
@@ -205,7 +205,33 @@ class TestsInbox(APITestCase):
         self.assertIn("https://distant.com/circle-members/1/", user_circles.values_list('urlid', flat=True))
         self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
 
-    # TODO: adding to a model which has multiple relationships with this RDF type
+    # TODO: https://git.startinblox.com/djangoldp-packages/djangoldp/issues/250
+    def test_add_activity_str_parameter(self):
+        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
+        UserProfile.objects.create(user=user)
+
+        payload = {
+            "@context": [
+                "https://www.w3.org/ns/activitystreams",
+                {"hd": "http://happy-dev.fr/owl/#"}
+            ],
+            "summary": "Test was added to Test Circle",
+            "type": "Add",
+            "actor": {
+                "type": "Service",
+                "name": "Backlinks Service"
+            },
+            "object": "https://distant.com/somethingunknown/1/",
+            "target": {
+                "@type": "foaf:user",
+                "@id": user.urlid
+            }
+        }
+
+        response = self.client.post('/inbox/',
+                                    data=json.dumps(payload),
+                                    content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
+        self.assertEqual(response.status_code, 400)
 
     # error behaviour - unknown model
     def test_add_activity_unknown(self):
@@ -239,6 +265,32 @@ class TestsInbox(APITestCase):
                                     data=json.dumps(payload), content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
         self.assertEqual(response.status_code, 404)
 
+    def test_invalid_activity_missing_actor(self):
+        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
+        UserProfile.objects.create(user=user)
+
+        payload = {
+            "@context": [
+                "https://www.w3.org/ns/activitystreams",
+                {"hd": "http://happy-dev.fr/owl/#"}
+            ],
+            "summary": "Test was added to Test Circle",
+            "type": "Add",
+            "object": {
+                "@type": "hd:somethingunknown",
+                "@id": "https://distant.com/somethingunknown/1/"
+            },
+            "target": {
+                "@type": "foaf:user",
+                "@id": user.urlid
+            }
+        }
+
+        response = self.client.post('/inbox/',
+                                    data=json.dumps(payload),
+                                    content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
+        self.assertEqual(response.status_code, 400)
+
     #
     #   REMOVE & DELETE ACTIVITIES
     #
@@ -288,7 +340,49 @@ class TestsInbox(APITestCase):
         self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
 
     # TODO: test_remove_activity_project_using_target
-    # TODO: error behaviour - project does not exist on user
+
+    # error behaviour - project does not exist on user
+    def test_remove_activity_nonexistent_project(self):
+        user = get_user_model().objects.create(username='john', email='jlennon@beatles.com', password='glass onion')
+        UserProfile.objects.create(user=user)
+        Project.objects.create(urlid="https://distant.com/projects/1/")
+
+        payload = {
+            "@context": [
+                "https://www.w3.org/ns/activitystreams",
+                {"hd": "http://happy-dev.fr/owl/#"}
+            ],
+            "summary": user.get_full_name() + " removed Test Project",
+            "type": "Remove",
+            "actor": {
+              "type": "Service",
+              "name": "Backlinks Service"
+            },
+            "object": {
+                "@type": "hd:project",
+                "@id": "https://distant.com/projects/1/"
+            },
+            "origin": {
+                "@type": "foaf:user",
+                "name": user.get_full_name(),
+                "@id": user.urlid
+            }
+        }
+
+        response = self.client.post('/inbox/',
+                                    data=json.dumps(payload),
+                                    content_type='application/ld+json;profile="https://www.w3.org/ns/activitystreams"')
+        self.assertEqual(response.status_code, 201)
+
+        # assert that the circle backlink(s) were removed & activity were created
+        projects = Project.objects.all()
+        user_projects = user.projects.all()
+        activities = Activity.objects.all()
+        self.assertEquals(len(projects), 1)
+        self.assertEquals(len(user_projects), 0)
+        self.assertEquals(len(activities), 1)
+        self.assertIn("https://distant.com/projects/1/", projects.values_list('urlid', flat=True))
+        self.assertIn(response["Location"], activities.values_list('urlid', flat=True))
 
     # Delete CircleMember
     def test_delete_activity_circle_using_origin(self):
diff --git a/djangoldp/views.py b/djangoldp/views.py
index 1dd61ecfba0d2d020a316293e5ffbe866fccdcc6..70a71ee5dd779520b45e6e1cb4146b58ecedf680 100644
--- a/djangoldp/views.py
+++ b/djangoldp/views.py
@@ -26,7 +26,7 @@ from djangoldp.models import LDPSource, Model, Activity, Follower
 from djangoldp.permissions import LDPPermissions
 from djangoldp.filters import LocalObjectOnContainerPathBackend
 from djangoldp.activities import ActivityPubService, as_activitystream
-from djangoldp.activities.errors import ActivityStreamDecodeError
+from djangoldp.activities.errors import ActivityStreamDecodeError, ActivityStreamValidationError
 
 
 get_user_model()._meta.rdf_context = {"get_full_name": "rdfs:label"}
@@ -82,6 +82,8 @@ class InboxView(APIView):
             activity.validate()
         except ActivityStreamDecodeError:
             return Response('Activity type unsupported', status=status.HTTP_405_METHOD_NOT_ALLOWED)
+        except ActivityStreamValidationError as e:
+            return Response(str(e), status=status.HTTP_400_BAD_REQUEST)
 
         if activity.type == 'Add':
             self.handle_add_activity(activity, **kwargs)
@@ -107,7 +109,7 @@ class InboxView(APIView):
 
     def get_or_create_nested_backlinks(self, object, object_model=None, update=False):
         '''
-        recursively constructs a tree of nested objects, using get_or_create on each leaf/branch
+        recursively deconstructs a tree of nested objects, using get_or_create on each leaf/branch
         :param object: Dict representation of the object
         :param object_model: The Model class of the object. Will be discovered if set to None
         :param update: if True will update retrieved objects with new data
@@ -149,19 +151,20 @@ class InboxView(APIView):
         object_model = self._get_subclass_with_rdf_type_or_404(activity.object['@type'])
         target_model = self._get_subclass_with_rdf_type_or_404(activity.target['@type'])
 
+        try:
+            target = target_model.objects.get(urlid=activity.target['@id'])
+        except target_model.DoesNotExist:
+            return Response({}, status=status.HTTP_404_NOT_FOUND)
+
         # store backlink(s) in database
         backlink = self.get_or_create_nested_backlinks(activity.object, object_model)
 
         # add object to target
-        try:
-            target_info = model_meta.get_field_info(target_model)
-            target = target_model.objects.get(urlid=activity.target['@id'])
+        target_info = model_meta.get_field_info(target_model)
 
-            for field_name, relation_info in target_info.relations.items():
-                if relation_info.related_model == object_model:
-                    getattr(target, field_name).add(backlink)
-        except target_model.DoesNotExist:
-            return Response({}, status=status.HTTP_404_NOT_FOUND)
+        for field_name, relation_info in target_info.relations.items():
+            if relation_info.related_model == object_model:
+                getattr(target, field_name).add(backlink)
 
     def handle_remove_activity(self, activity, **kwargs):
         '''
@@ -174,21 +177,19 @@ class InboxView(APIView):
 
         # get the model reference to saved object
         try:
+            origin = origin_model.objects.get(urlid=activity.origin['@id'])
             object_instance = object_model.objects.get(urlid=activity.object['@id'])
+        except origin_model.DoesNotExist:
+            raise Http404()
         except object_model.DoesNotExist:
             return
 
         # remove object from origin
-        try:
-            origin_info = model_meta.get_field_info(origin_model)
-            origin = origin_model.objects.get(urlid=activity.origin['@id'])
+        origin_info = model_meta.get_field_info(origin_model)
 
-            for field_name, relation_info in origin_info.relations.items():
-                if relation_info.related_model == object_model:
-                    getattr(origin, field_name).remove(object_instance)
-        # TODO: decipher from history if the resource has been moved?
-        except origin_model.DoesNotExist:
-            raise Http404()
+        for field_name, relation_info in origin_info.relations.items():
+            if relation_info.related_model == object_model:
+                getattr(origin, field_name).remove(object_instance)
 
     def handle_create_or_update_activity(self, activity, **kwargs):
         '''