diff --git a/briar-api/src/main/java/org/briarproject/briar/api/sharing/InvitationFactory.java b/briar-api/src/main/java/org/briarproject/briar/api/sharing/InvitationFactory.java
index eab12f74142f15539b4f8f9a2dae4c5fa93a0a9f..5b4e275a3958588e71a66d725f06aef9474ac332 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/sharing/InvitationFactory.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/sharing/InvitationFactory.java
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.sync.GroupId;
 
+@Deprecated
 public interface InvitationFactory<I extends SharingMessage.Invitation> {
 
 	I build(GroupId groupId, BdfDictionary d) throws FormatException;
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingConstants.java
index 53f4557e45e3ff86702d55fb0d6aba2ad6d84986..6b8fcc4a058a1fcbb1b6708a51a3ec97f108d617 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingConstants.java
@@ -15,34 +15,63 @@ public interface SharingConstants {
 	 */
 	int MAX_INVITATION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
 
+	@Deprecated
 	String CONTACT_ID = "contactId";
+	@Deprecated
 	String GROUP_ID = "groupId";
+	@Deprecated
 	String TO_BE_SHARED_BY_US = "toBeSharedByUs";
+	@Deprecated
 	String SHARED_BY_US = "sharedByUs";
+	@Deprecated
 	String SHARED_WITH_US = "sharedWithUs";
+	@Deprecated
 	String TYPE = "type";
+	@Deprecated
 	String SESSION_ID = "sessionId";
+	@Deprecated
 	String STORAGE_ID = "storageId";
+	@Deprecated
 	String STATE = "state";
+	@Deprecated
 	String LOCAL = "local";
+	@Deprecated
 	String TIME = "time";
+	@Deprecated
 	String IS_SHARER = "isSharer";
+	@Deprecated
 	String SHAREABLE_ID = "shareableId";
+	@Deprecated
 	String INVITATION_MSG = "invitationMsg";
+	@Deprecated
 	String INVITATION_ID = "invitationId";
+	@Deprecated
 	String RESPONSE_ID = "responseId";
+	@Deprecated
 	int SHARE_MSG_TYPE_INVITATION = 1;
+	@Deprecated
 	int SHARE_MSG_TYPE_ACCEPT = 2;
+	@Deprecated
 	int SHARE_MSG_TYPE_DECLINE = 3;
+	@Deprecated
 	int SHARE_MSG_TYPE_LEAVE = 4;
+	@Deprecated
 	int SHARE_MSG_TYPE_ABORT = 5;
+	@Deprecated
 	int TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US = 0;
+	@Deprecated
 	int TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US = 1;
+	@Deprecated
 	int TASK_ADD_SHARED_SHAREABLE = 2;
+	@Deprecated
 	int TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US = 3;
+	@Deprecated
 	int TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US = 4;
+	@Deprecated
 	int TASK_SHARE_SHAREABLE = 5;
+	@Deprecated
 	int TASK_UNSHARE_SHAREABLE_SHARED_BY_US = 6;
+	@Deprecated
 	int TASK_UNSHARE_SHAREABLE_SHARED_WITH_US = 7;
 
 }
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingMessage.java b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingMessage.java
index 23340ac1c012f35a61407bfbc8db4e06279b8865..dd4b987aa95d6b3283632c147b3980f350bf00d3 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingMessage.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingMessage.java
@@ -21,6 +21,7 @@ import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE
 import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
 import static org.briarproject.briar.api.sharing.SharingConstants.TYPE;
 
+@Deprecated
 @NotNullByDefault
 public interface SharingMessage {
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java
index 36c00ffb1000a65e54810ac48187b43380d798af..30c1401197607a10ad93dba778f84d49860f0177 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
@@ -177,7 +176,7 @@ abstract class AbstractProtocolEngine<S extends Session>
 
 	void subscribeToPrivateGroup(Transaction txn, MessageId inviteId)
 			throws DbException, FormatException {
-		InviteMessage invite = getInviteMessage(txn, inviteId);
+		InviteMessage invite = messageParser.getInviteMessage(txn, inviteId);
 		PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
 				invite.getGroupName(), invite.getCreator(), invite.getSalt());
 		long timestamp =
@@ -197,14 +196,6 @@ abstract class AbstractProtocolEngine<S extends Session>
 						session.getInviteTimestamp()) + 1);
 	}
 
-	private InviteMessage getInviteMessage(Transaction txn, MessageId m)
-			throws DbException, FormatException {
-		Message message = clientHelper.getMessage(txn, m);
-		if (message == null) throw new DbException();
-		BdfList body = clientHelper.toList(message);
-		return messageParser.parseInviteMessage(message, body);
-	}
-
 	private void sendMessage(Transaction txn, Message m, MessageType type,
 			GroupId privateGroupId, boolean visibleInConversation)
 			throws DbException {
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java
index 9e19966d8f9cd519d7ce361b93febde09f881d9d..1fac77c630aa1e049776b518c0217df1f7f0f141 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java
@@ -401,7 +401,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
 			throws DbException, FormatException {
 		SessionId sessionId = getSessionId(meta.getPrivateGroupId());
 		// Look up the invite message to get the details of the private group
-		InviteMessage invite = getInviteMessage(txn, m);
+		InviteMessage invite = messageParser.getInviteMessage(txn, m);
 		PrivateGroup pg = privateGroupFactory
 				.createPrivateGroup(invite.getGroupName(), invite.getCreator(),
 						invite.getSalt());
@@ -412,14 +412,6 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
 				invite.getMessage(), meta.isAvailableToAnswer(), canBeOpened);
 	}
 
-	private InviteMessage getInviteMessage(Transaction txn, MessageId m)
-			throws DbException, FormatException {
-		Message message = clientHelper.getMessage(txn, m);
-		if (message == null) throw new DbException();
-		BdfList body = clientHelper.toList(message);
-		return messageParser.parseInviteMessage(message, body);
-	}
-
 	private GroupInvitationResponse parseInvitationResponse(ContactId c,
 			GroupId contactGroupId, MessageId m, MessageMetadata meta,
 			MessageStatus status, boolean accept)
@@ -479,7 +471,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
 
 	private GroupInvitationItem parseGroupInvitationItem(Transaction txn,
 			Contact c, MessageId m) throws DbException, FormatException {
-		InviteMessage invite = getInviteMessage(txn, m);
+		InviteMessage invite = messageParser.getInviteMessage(txn, m);
 		PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
 				invite.getGroupName(), invite.getCreator(), invite.getSalt());
 		return new GroupInvitationItem(privateGroup, c);
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParser.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParser.java
index 9e6a7ad9dbeb423a32e526983162a46f7a829c8e..8300b305c8c8ba7d299a40694aa56f5cfa85026d 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParser.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParser.java
@@ -3,9 +3,12 @@ package org.briarproject.briar.privategroup.invitation;
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.MessageId;
 
 @NotNullByDefault
 interface MessageParser {
@@ -18,6 +21,9 @@ interface MessageParser {
 
 	MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException;
 
+	InviteMessage getInviteMessage(Transaction txn, MessageId m)
+			throws DbException, FormatException;
+
 	InviteMessage parseInviteMessage(Message m, BdfList body)
 			throws FormatException;
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParserImpl.java
index b31ff7d3174240e7a1c85d1edab0f06d288fb702..1cf35ee9c32b740382678c5bdcdcb7de473c678e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParserImpl.java
@@ -1,9 +1,12 @@
 package org.briarproject.briar.privategroup.invitation;
 
 import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -31,12 +34,14 @@ class MessageParserImpl implements MessageParser {
 
 	private final AuthorFactory authorFactory;
 	private final PrivateGroupFactory privateGroupFactory;
+	private final ClientHelper clientHelper;
 
 	@Inject
 	MessageParserImpl(AuthorFactory authorFactory,
-			PrivateGroupFactory privateGroupFactory) {
+			PrivateGroupFactory privateGroupFactory, ClientHelper clientHelper) {
 		this.authorFactory = authorFactory;
 		this.privateGroupFactory = privateGroupFactory;
+		this.clientHelper = clientHelper;
 	}
 
 	@Override
@@ -78,6 +83,15 @@ class MessageParserImpl implements MessageParser {
 				visible, available);
 	}
 
+	@Override
+	public InviteMessage getInviteMessage(Transaction txn, MessageId m)
+			throws DbException, FormatException {
+		Message message = clientHelper.getMessage(txn, m);
+		if (message == null) throw new DbException();
+		BdfList body = clientHelper.toList(message);
+		return parseInviteMessage(message, body);
+	}
+
 	@Override
 	public InviteMessage parseInviteMessage(Message m, BdfList body)
 			throws FormatException {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/AbortMessage.java b/briar-core/src/main/java/org/briarproject/briar/sharing/AbortMessage.java
index 872ab42d1399ec6afb08b8d1cb816207709e8c48..188c0b5013aa017336a2ace5aeb3a769844784b5 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/AbortMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/AbortMessage.java
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
 
 @Immutable
 @NotNullByDefault
-public class AbortMessage extends SharingMessage {
+class AbortMessage extends SharingMessage {
 
 	AbortMessage(MessageId id, GroupId contactGroupId, GroupId shareableId,
 			long timestamp, @Nullable MessageId previousMessageId) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/AcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/sharing/AcceptMessage.java
index 81d7df8156515de32dca493a4bed90d7d0903c54..0a440a5cd2bccbba67992306f523af925914c589 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/AcceptMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/AcceptMessage.java
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
 
 @Immutable
 @NotNullByDefault
-public class AcceptMessage extends SharingMessage {
+class AcceptMessage extends SharingMessage {
 
 	AcceptMessage(MessageId id, @Nullable MessageId previousMessageId,
 			GroupId contactGroupId, GroupId shareableId, long timestamp) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java
index c0bd11ca90dfb5f183d8e4f332f28791ac1cb4bb..a436dd9a39bb7c3ae826fb21a3b3f52b3fd68be7 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java
@@ -159,7 +159,7 @@ class BlogSharingManagerImpl extends
 	}
 
 	@Override
-	protected InvitationFactory<BlogInvitation, BlogSharerSessionState> getIFactory() {
+	protected OldInvitationFactory<BlogInvitation, BlogSharerSessionState> getIFactory() {
 		return iFactory;
 	}
 
@@ -251,7 +251,7 @@ class BlogSharingManagerImpl extends
 	}
 
 	private static class IFactory implements
-			InvitationFactory<BlogInvitation, BlogSharerSessionState> {
+			OldInvitationFactory<BlogInvitation, BlogSharerSessionState> {
 		@Override
 		public BlogInvitation build(GroupId groupId, BdfDictionary d)
 				throws FormatException {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/DeclineMessage.java b/briar-core/src/main/java/org/briarproject/briar/sharing/DeclineMessage.java
index 9ae35ece6c0286bfe99969cf104bf489232464f5..65f9a9fe12d00458d506bdd75fb78e713c3693ea 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/DeclineMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/DeclineMessage.java
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
 
 @Immutable
 @NotNullByDefault
-public class DeclineMessage extends SharingMessage {
+class DeclineMessage extends SharingMessage {
 
 	DeclineMessage(MessageId id, GroupId contactGroupId,
 			GroupId shareableId, long timestamp,
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumInvitationFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumInvitationFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..591f25bc97af0dc93dc956e5d5943a2369d21d24
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumInvitationFactoryImpl.java
@@ -0,0 +1,39 @@
+package org.briarproject.briar.sharing;
+
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.bramble.api.sync.MessageId;
+import org.briarproject.briar.api.client.SessionId;
+import org.briarproject.briar.api.forum.Forum;
+import org.briarproject.briar.api.forum.ForumInvitationRequest;
+import org.briarproject.briar.api.forum.ForumInvitationResponse;
+
+import javax.inject.Inject;
+
+public class ForumInvitationFactoryImpl implements InvitationFactory<Forum> {
+
+	@Inject
+	ForumInvitationFactoryImpl() {
+	}
+
+	@Override
+	public ForumInvitationRequest createInvitationRequest(boolean local,
+			boolean sent, boolean seen, boolean read, InviteMessage<Forum> m,
+			ContactId c, boolean available, boolean canBeOpened) {
+		SessionId sessionId = new SessionId(m.getShareableId().getBytes());
+		return new ForumInvitationRequest(m.getId(), m.getContactGroupId(),
+				m.getTimestamp(), local, sent, seen, read, sessionId,
+				m.getShareable(), c, m.getMessage(), available, canBeOpened);
+	}
+
+	@Override
+	public ForumInvitationResponse createInvitationResponse(MessageId id,
+			GroupId contactGroupId, long time, boolean local, boolean sent,
+			boolean seen, boolean read, GroupId shareableId,
+			ContactId contactId, boolean accept) {
+		SessionId sessionId = new SessionId(shareableId.getBytes());
+		return new ForumInvitationResponse(id, contactGroupId, time, local,
+				sent, seen, read, sessionId, shareableId, contactId, accept);
+	}
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumMessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumMessageParserImpl.java
index 1e542b7601ddabcb475ccde2139517aafdd7e882..ed1ca425cf76bd504a0421f0bf1b04cd0712de3b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumMessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumMessageParserImpl.java
@@ -1,6 +1,7 @@
 package org.briarproject.briar.sharing;
 
 import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.briar.api.forum.Forum;
@@ -16,8 +17,9 @@ class ForumMessageParserImpl extends MessageParserImpl<Forum> {
 	private final ForumFactory forumFactory;
 
 	@Inject
-	ForumMessageParserImpl(ForumFactory forumFactory) {
-		super();
+	ForumMessageParserImpl(ClientHelper clientHelper,
+			ForumFactory forumFactory) {
+		super(clientHelper);
 		this.forumFactory = forumFactory;
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumProtocolEngineImpl.java
index 3e89fab90825ab51e81b48f3d76f61104d1e7390..0dd97300b04208ef861fea585f3682dfc50377ea 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumProtocolEngineImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumProtocolEngineImpl.java
@@ -3,22 +3,20 @@ package org.briarproject.briar.sharing;
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.ClientId;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.briar.api.client.MessageTracker;
-import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.forum.Forum;
 import org.briarproject.briar.api.forum.ForumInvitationRequest;
 import org.briarproject.briar.api.forum.ForumInvitationResponse;
 import org.briarproject.briar.api.forum.ForumManager;
+import org.briarproject.briar.api.forum.ForumSharingManager;
 import org.briarproject.briar.api.forum.event.ForumInvitationRequestReceivedEvent;
 import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent;
 
@@ -30,23 +28,27 @@ import javax.inject.Inject;
 class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> {
 
 	private final ForumManager forumManager;
+	private final InvitationFactory<Forum> invitationFactory;
 
 	@Inject
 	ForumProtocolEngineImpl(DatabaseComponent db,
 			ClientHelper clientHelper, MessageEncoder messageEncoder,
 			MessageParser<Forum> messageParser, MessageTracker messageTracker,
-			Clock clock, ForumManager forumManager) {
+			Clock clock, ForumManager forumManager,
+			InvitationFactory<Forum> invitationFactory) {
 		super(db, clientHelper, messageEncoder, messageParser, messageTracker,
 				clock);
 		this.forumManager = forumManager;
+		this.invitationFactory = invitationFactory;
 	}
 
 	@Override
 	Event getInvitationRequestReceivedEvent(InviteMessage<Forum> m,
 			ContactId contactId, boolean available, boolean canBeOpened) {
 		ForumInvitationRequest request =
-				createInvitationRequest(false, false, true, false, m, contactId,
-						available, canBeOpened);
+				(ForumInvitationRequest) invitationFactory
+						.createInvitationRequest(false, false, true, false, m,
+								contactId, available, canBeOpened);
 		return new ForumInvitationRequestReceivedEvent(m.getShareable(),
 				contactId, request);
 	}
@@ -55,9 +57,11 @@ class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> {
 	Event getInvitationResponseReceivedEvent(AcceptMessage m,
 			ContactId contactId) {
 		ForumInvitationResponse response =
-				createInvitationResponse(m.getId(), m.getContactGroupId(),
-						m.getTimestamp(), false, false, true, false,
-						m.getShareableId(), contactId, true);
+				(ForumInvitationResponse) invitationFactory
+						.createInvitationResponse(m.getId(),
+								m.getContactGroupId(), m.getTimestamp(), false,
+								false, true, false, m.getShareableId(),
+								contactId, true);
 		return new ForumInvitationResponseReceivedEvent(contactId, response);
 	}
 
@@ -65,45 +69,25 @@ class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> {
 	Event getInvitationResponseReceivedEvent(DeclineMessage m,
 			ContactId contactId) {
 		ForumInvitationResponse response =
-				createInvitationResponse(m.getId(), m.getContactGroupId(),
-						m.getTimestamp(), false, false, true, false,
-						m.getShareableId(), contactId, true);
+				(ForumInvitationResponse) invitationFactory
+						.createInvitationResponse(m.getId(),
+								m.getContactGroupId(), m.getTimestamp(), false,
+								false, true, false, m.getShareableId(),
+								contactId, true);
 		return new ForumInvitationResponseReceivedEvent(contactId, response);
 	}
 
 	@Override
-	protected void addShareable(Transaction txn, MessageId inviteId)
-			throws DbException, FormatException {
-		InviteMessage<Forum> invite = getInviteMessage(txn, inviteId);
-		forumManager.addForum(txn, invite.getShareable());
-	}
-
-	private InviteMessage<Forum> getInviteMessage(Transaction txn, MessageId m)
-			throws DbException, FormatException {
-		Message message = clientHelper.getMessage(txn, m);
-		if (message == null) throw new DbException();
-		BdfList body = clientHelper.toList(message);
-		return messageParser.parseInviteMessage(message, body);
+	protected ClientId getClientId() {
+		return ForumSharingManager.CLIENT_ID;
 	}
 
 	@Override
-	public ForumInvitationRequest createInvitationRequest(boolean local,
-			boolean sent, boolean seen, boolean read, InviteMessage<Forum> m,
-			ContactId c, boolean available, boolean canBeOpened) {
-		SessionId sessionId = new SessionId(m.getShareableId().getBytes());
-		return new ForumInvitationRequest(m.getId(), m.getContactGroupId(),
-				m.getTimestamp(), local, sent, seen, read, sessionId,
-				m.getShareable(), c, m.getMessage(), available, canBeOpened);
-	}
-
-	@Override
-	public ForumInvitationResponse createInvitationResponse(MessageId id,
-			GroupId contactGroupId, long time, boolean local, boolean sent,
-			boolean seen, boolean read, GroupId shareableId,
-			ContactId contactId, boolean accept) {
-		SessionId sessionId = new SessionId(shareableId.getBytes());
-		return new ForumInvitationResponse(id, contactGroupId, time, local,
-				sent, seen, read, sessionId, shareableId, contactId, accept);
+	protected void addShareable(Transaction txn, MessageId inviteId)
+			throws DbException, FormatException {
+		InviteMessage<Forum> invite =
+				messageParser.getInviteMessage(txn, inviteId);
+		forumManager.addForum(txn, invite.getShareable());
 	}
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingManagerImpl.java
index 24a04a75297bffc9a50703b6b1b78fb0de725264..a2e8d18d9742ab3b5db1d81b05005a8c90a7b943 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingManagerImpl.java
@@ -25,9 +25,11 @@ class ForumSharingManagerImpl extends SharingManagerImpl<Forum>
 			SessionEncoder sessionEncoder, SessionParser sessionParser,
 			MessageTracker messageTracker,
 			ContactGroupFactory contactGroupFactory,
-			ProtocolEngine<Forum> engine) {
+			ProtocolEngine<Forum> engine,
+			InvitationFactory<Forum> invitationFactory) {
 		super(db, clientHelper, metadataParser, messageParser, sessionEncoder,
-				sessionParser, messageTracker, contactGroupFactory, engine);
+				sessionParser, messageTracker, contactGroupFactory, engine,
+				invitationFactory);
 	}
 
 	@Override
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationFactory.java b/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationFactory.java
index cc704e6fa7a0a311c94d6e8b9da29f8d807259d3..ab9fa0e23df0ae810227d9352830cadcd5348b20 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationFactory.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationFactory.java
@@ -1,12 +1,21 @@
 package org.briarproject.briar.sharing;
 
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.briar.api.sharing.SharingMessage;
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.bramble.api.sync.MessageId;
+import org.briarproject.briar.api.sharing.InvitationRequest;
+import org.briarproject.briar.api.sharing.InvitationResponse;
+import org.briarproject.briar.api.sharing.Shareable;
 
-@Deprecated
-@NotNullByDefault
-interface InvitationFactory<I extends SharingMessage.Invitation, SS extends SharerSessionState>
-		extends org.briarproject.briar.api.sharing.InvitationFactory<I> {
+public interface InvitationFactory<S extends Shareable> {
+
+	InvitationRequest<S> createInvitationRequest(boolean local, boolean sent,
+			boolean seen, boolean read, InviteMessage<S> m, ContactId c,
+			boolean available, boolean canBeOpened);
+
+	InvitationResponse createInvitationResponse(MessageId id,
+			GroupId contactGroupId, long time, boolean local, boolean sent,
+			boolean seen, boolean read, GroupId shareableId,
+			ContactId contactId, boolean accept);
 
-	I build(SS localState, long time);
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/InviteMessage.java b/briar-core/src/main/java/org/briarproject/briar/sharing/InviteMessage.java
index 31b53efe92196302409171d2d8067e12250effdf..f6343cd8db2cb4656af61208fdc248cf3bd16d7b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/InviteMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/InviteMessage.java
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
 
 @Immutable
 @NotNullByDefault
-public class InviteMessage<S extends Shareable> extends SharingMessage {
+class InviteMessage<S extends Shareable> extends SharingMessage {
 
 	private final S shareable;
 	@Nullable
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/LeaveMessage.java b/briar-core/src/main/java/org/briarproject/briar/sharing/LeaveMessage.java
index 33953bc31ec23107c1d253dd6901595cefddfe7e..f518570551a286d843616f0792a76f961f03a843 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/LeaveMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/LeaveMessage.java
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
 
 @Immutable
 @NotNullByDefault
-public class LeaveMessage extends SharingMessage {
+class LeaveMessage extends SharingMessage {
 
 	LeaveMessage(MessageId id, GroupId contactGroupId,
 			GroupId shareableId, long timestamp,
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoder.java
index d19d55bdfa28491247e7fce29db3624d1dd859e9..33ce9b3fc1b160398913f7b205c89080dad838a3 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoder.java
@@ -12,7 +12,7 @@ import javax.annotation.Nullable;
 @NotNullByDefault
 interface MessageEncoder {
 
-	BdfDictionary encodeMetadata(MessageType type, GroupId groupId,
+	BdfDictionary encodeMetadata(MessageType type, GroupId shareableId,
 			long timestamp, boolean local, boolean read, boolean visible,
 			boolean available);
 
@@ -24,16 +24,16 @@ interface MessageEncoder {
 			@Nullable MessageId previousMessageId, BdfList descriptor,
 			@Nullable String message);
 
-	Message encodeAcceptMessage(GroupId contactGroupId, GroupId groupId,
+	Message encodeAcceptMessage(GroupId contactGroupId, GroupId shareableId,
 			long timestamp, @Nullable MessageId previousMessageId);
 
-	Message encodeDeclineMessage(GroupId contactGroupId, GroupId groupId,
+	Message encodeDeclineMessage(GroupId contactGroupId, GroupId shareableId,
 			long timestamp, @Nullable MessageId previousMessageId);
 
-	Message encodeLeaveMessage(GroupId contactGroupId, GroupId groupId,
+	Message encodeLeaveMessage(GroupId contactGroupId, GroupId shareableId,
 			long timestamp, @Nullable MessageId previousMessageId);
 
-	Message encodeAbortMessage(GroupId contactGroupId, GroupId groupId,
+	Message encodeAbortMessage(GroupId contactGroupId, GroupId shareableId,
 			long timestamp, @Nullable MessageId previousMessageId);
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoderImpl.java
index 187cafe56b2ef1fbae64f0b56d7143dc0e5c80af..1047df487e05596c61a603bc6973e759a2842809 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoderImpl.java
@@ -43,11 +43,11 @@ class MessageEncoderImpl implements MessageEncoder {
 
 	@Override
 	public BdfDictionary encodeMetadata(MessageType type,
-			GroupId groupId, long timestamp, boolean local, boolean read,
+			GroupId shareableId, long timestamp, boolean local, boolean read,
 			boolean visible, boolean available) {
 		BdfDictionary meta = new BdfDictionary();
 		meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
-		meta.put(MSG_KEY_SHAREABLE_ID, groupId);
+		meta.put(MSG_KEY_SHAREABLE_ID, shareableId);
 		meta.put(MSG_KEY_TIMESTAMP, timestamp);
 		meta.put(MSG_KEY_LOCAL, local);
 		meta.put(MSG_KEY_READ, read);
@@ -88,42 +88,42 @@ class MessageEncoderImpl implements MessageEncoder {
 
 	@Override
 	public Message encodeAcceptMessage(GroupId contactGroupId,
-			GroupId privateGroupId, long timestamp,
+			GroupId shareableId, long timestamp,
 			@Nullable MessageId previousMessageId) {
-		return encodeMessage(ACCEPT, contactGroupId, privateGroupId, timestamp,
+		return encodeMessage(ACCEPT, contactGroupId, shareableId, timestamp,
 				previousMessageId);
 	}
 
 	@Override
 	public Message encodeDeclineMessage(GroupId contactGroupId,
-			GroupId privateGroupId, long timestamp,
+			GroupId shareableId, long timestamp,
 			@Nullable MessageId previousMessageId) {
-		return encodeMessage(DECLINE, contactGroupId, privateGroupId, timestamp,
+		return encodeMessage(DECLINE, contactGroupId, shareableId, timestamp,
 				previousMessageId);
 	}
 
 	@Override
 	public Message encodeLeaveMessage(GroupId contactGroupId,
-			GroupId privateGroupId, long timestamp,
+			GroupId shareableId, long timestamp,
 			@Nullable MessageId previousMessageId) {
-		return encodeMessage(LEAVE, contactGroupId, privateGroupId, timestamp,
+		return encodeMessage(LEAVE, contactGroupId, shareableId, timestamp,
 				previousMessageId);
 	}
 
 	@Override
 	public Message encodeAbortMessage(GroupId contactGroupId,
-			GroupId privateGroupId, long timestamp,
+			GroupId shareableId, long timestamp,
 			@Nullable MessageId previousMessageId) {
-		return encodeMessage(ABORT, contactGroupId, privateGroupId, timestamp,
+		return encodeMessage(ABORT, contactGroupId, shareableId, timestamp,
 				previousMessageId);
 	}
 
 	private Message encodeMessage(MessageType type, GroupId contactGroupId,
-			GroupId groupId, long timestamp,
+			GroupId shareableId, long timestamp,
 			@Nullable MessageId previousMessageId) {
 		BdfList body = BdfList.of(
 				type.getValue(),
-				groupId,
+				shareableId,
 				previousMessageId
 		);
 		try {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageParser.java b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageParser.java
index 17d9f73b145d1b9f401e63baf82546c917d5210b..869d017ad1a2c0427a54b5d257ad1a9815fd96bc 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageParser.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageParser.java
@@ -3,9 +3,12 @@ package org.briarproject.briar.sharing;
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.briar.api.sharing.Shareable;
 
 @NotNullByDefault
@@ -15,10 +18,13 @@ interface MessageParser<S extends Shareable> {
 
 	BdfDictionary getInvitesAvailableToAnswerQuery();
 
-	BdfDictionary getInvitesAvailableToAnswerQuery(GroupId privateGroupId);
+	BdfDictionary getInvitesAvailableToAnswerQuery(GroupId shareableId);
 
 	MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException;
 
+	InviteMessage<S> getInviteMessage(Transaction txn, MessageId m)
+			throws DbException, FormatException;
+
 	InviteMessage<S> parseInviteMessage(Message m, BdfList body)
 			throws FormatException;
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageParserImpl.java
index 74be4c5ca923ffd92ec94bd14e8d2da58974370c..4384941179563df52a39de121ef693ea38e581d8 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageParserImpl.java
@@ -1,9 +1,12 @@
 package org.briarproject.briar.sharing;
 
 import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
@@ -26,7 +29,10 @@ import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_VISIBLE_IN
 abstract class MessageParserImpl<S extends Shareable>
 		implements MessageParser<S> {
 
-	MessageParserImpl() {
+	private final ClientHelper clientHelper;
+
+	MessageParserImpl(ClientHelper clientHelper) {
+		this.clientHelper = clientHelper;
 	}
 
 	@Override
@@ -66,6 +72,15 @@ abstract class MessageParserImpl<S extends Shareable>
 				visible, available);
 	}
 
+	@Override
+	public InviteMessage<S> getInviteMessage(Transaction txn, MessageId m)
+			throws DbException, FormatException {
+		Message message = clientHelper.getMessage(txn, m);
+		if (message == null) throw new DbException();
+		BdfList body = clientHelper.toList(message);
+		return parseInviteMessage(message, body);
+	}
+
 	@Override
 	public InviteMessage<S> parseInviteMessage(Message m, BdfList body)
 			throws FormatException {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/OldInvitationFactory.java b/briar-core/src/main/java/org/briarproject/briar/sharing/OldInvitationFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f127a1c826251c03914e9479d10a32b74edd6b3
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/OldInvitationFactory.java
@@ -0,0 +1,12 @@
+package org.briarproject.briar.sharing;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.briar.api.sharing.SharingMessage;
+
+@Deprecated
+@NotNullByDefault
+interface OldInvitationFactory<I extends SharingMessage.Invitation, SS extends SharerSessionState>
+		extends org.briarproject.briar.api.sharing.InvitationFactory<I> {
+
+	I build(SS localState, long time);
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/OldSharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/OldSharingManagerImpl.java
index 0284e980d7b5b20c193cad15ffac493f989e9018..613ee0bed8aa2570a15b45ccab6c579a4df3fa5d 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/OldSharingManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/OldSharingManagerImpl.java
@@ -137,7 +137,7 @@ abstract class OldSharingManagerImpl<S extends Shareable, I extends Invitation,
 
 	protected abstract ShareableFactory<S, I, IS, SS> getSFactory();
 
-	protected abstract InvitationFactory<I, SS> getIFactory();
+	protected abstract OldInvitationFactory<I, SS> getIFactory();
 
 	protected abstract InviteeSessionStateFactory<S, IS> getISFactory();
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngine.java
index 57945d4efbbb79541958863ae762e564fd91804d..4277303c628cdaf5581e325325ca3f82a7e98fcb 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngine.java
@@ -1,14 +1,9 @@
 package org.briarproject.briar.sharing;
 
 import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.briar.api.sharing.InvitationRequest;
-import org.briarproject.briar.api.sharing.InvitationResponse;
 import org.briarproject.briar.api.sharing.Shareable;
 
 import javax.annotation.Nullable;
@@ -41,13 +36,4 @@ interface ProtocolEngine<S extends Shareable> {
 	Session onAbortMessage(Transaction txn, Session session, AbortMessage m)
 			throws DbException, FormatException;
 
-	InvitationRequest<S> createInvitationRequest(boolean local, boolean sent,
-			boolean seen, boolean read, InviteMessage<S> m, ContactId c,
-			boolean available, boolean canBeOpened);
-
-	InvitationResponse createInvitationResponse(MessageId id, GroupId groupId,
-			long time, boolean local, boolean sent, boolean seen,
-			boolean read, GroupId shareableId, ContactId contactId,
-			boolean accept);
-
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
index 3c54a62f70c0028de8212000d1bd8cdfb9cd89ea..dd8132d90aec63b30b489e3e3f55934697be584c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.sync.ClientId;
 import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.Group.Visibility;
 import org.briarproject.bramble.api.sync.GroupId;
@@ -27,6 +28,7 @@ import javax.annotation.concurrent.Immutable;
 
 import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
+import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
 import static org.briarproject.briar.sharing.MessageType.ABORT;
 import static org.briarproject.briar.sharing.MessageType.ACCEPT;
 import static org.briarproject.briar.sharing.MessageType.DECLINE;
@@ -91,11 +93,16 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		Message sent = sendInviteMessage(txn, s, message, timestamp);
 		// Track the message
 		messageTracker.trackOutgoingMessage(txn, sent);
+		// Make the shareable visible to the contact
+		try {
+			setShareableVisibility(txn, s, VISIBLE);
+		} catch (FormatException e) {
+			throw new DbException(e); // Invalid group metadata
+		}
 		// Move to the REMOTE_INVITED state
-		long localTimestamp = Math.max(timestamp, getLocalTimestamp(s));
 		return new Session(REMOTE_INVITED, s.getContactGroupId(),
 				s.getShareableId(), sent.getId(), s.getLastRemoteMessageId(),
-				localTimestamp, s.getInviteTimestamp());
+				sent.getTimestamp(), s.getInviteTimestamp());
 	}
 
 	private Message sendInviteMessage(Transaction txn, Session s,
@@ -107,8 +114,9 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		} catch (FormatException e) {
 			throw new DbException(e); // Invalid group descriptor
 		}
+		long localTimestamp = Math.max(timestamp, getLocalTimestamp(s));
 		Message m = messageEncoder
-				.encodeInviteMessage(s.getContactGroupId(), timestamp,
+				.encodeInviteMessage(s.getContactGroupId(), localTimestamp,
 						s.getLastLocalMessageId(), descriptor, message);
 		sendMessage(txn, m, INVITE, s.getShareableId(), true);
 		return m;
@@ -140,14 +148,14 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		if (inviteId == null) throw new IllegalStateException();
 		markMessageAvailableToAnswer(txn, inviteId, false);
 		// Send a ACCEPT message
-		Message sent = sendAcceptMessage(txn, s, true);
+		Message sent = sendAcceptMessage(txn, s);
 		// Track the message
 		messageTracker.trackOutgoingMessage(txn, sent);
 		try {
 			// Add and subscribe to the shareable
 			addShareable(txn, inviteId);
 			// Share the shareable with the contact
-			setPrivateGroupVisibility(txn, s, SHARED);
+			setShareableVisibility(txn, s, SHARED);
 		} catch (FormatException e) {
 			throw new DbException(e); // Invalid group metadata
 		}
@@ -160,12 +168,12 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 	protected abstract void addShareable(Transaction txn, MessageId inviteId)
 			throws DbException, FormatException;
 
-	private Message sendAcceptMessage(Transaction txn, Session session,
-			boolean visibleInUi) throws DbException {
+	private Message sendAcceptMessage(Transaction txn, Session session)
+			throws DbException {
 		Message m = messageEncoder.encodeAcceptMessage(
 				session.getContactGroupId(), session.getShareableId(),
 				getLocalTimestamp(session), session.getLastLocalMessageId());
-		sendMessage(txn, m, ACCEPT, session.getShareableId(), visibleInUi);
+		sendMessage(txn, m, ACCEPT, session.getShareableId(), true);
 		return m;
 	}
 
@@ -195,7 +203,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		if (inviteId == null) throw new IllegalStateException();
 		markMessageAvailableToAnswer(txn, inviteId, false);
 		// Send a DECLINE message
-		Message sent = sendDeclineMessage(txn, s, true);
+		Message sent = sendDeclineMessage(txn, s);
 		// Track the message
 		messageTracker.trackOutgoingMessage(txn, sent);
 		// Move to the START state
@@ -204,12 +212,12 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 				s.getInviteTimestamp());
 	}
 
-	private Message sendDeclineMessage(Transaction txn, Session session,
-			boolean visibleInUi) throws DbException {
+	private Message sendDeclineMessage(Transaction txn, Session session)
+			throws DbException {
 		Message m = messageEncoder.encodeDeclineMessage(
 				session.getContactGroupId(), session.getShareableId(),
 				getLocalTimestamp(session), session.getLastLocalMessageId());
-		sendMessage(txn, m, DECLINE, session.getShareableId(), visibleInUi);
+		sendMessage(txn, m, DECLINE, session.getShareableId(), true);
 		return m;
 	}
 
@@ -228,7 +236,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 			case LOCAL_LEFT:
 			case REMOTE_HANGING:
 			case ERROR:
-				throw new ProtocolStateException(); // Invalid in these states
+				return s; // Ignored in this state
 			default:
 				throw new AssertionError();
 		}
@@ -237,25 +245,25 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 	private Session onLocalLeave(Transaction txn, Session s, State nextState)
 			throws DbException {
 		try {
-			// Stop sharing the shareable with the contact
-			setPrivateGroupVisibility(txn, s, INVISIBLE);
+			// Stop sharing the shareable (not actually needed in REMOTE_LEFT)
+			setShareableVisibility(txn, s, INVISIBLE);
 		} catch (FormatException e) {
 			throw new DbException(e); // Invalid group metadata
 		}
 		// Send a LEAVE message
-		Message sent = sendLeaveMessage(txn, s, false);
+		Message sent = sendLeaveMessage(txn, s);
 		// Move to the next state
 		return new Session(nextState, s.getContactGroupId(), s.getShareableId(),
 				sent.getId(), s.getLastRemoteMessageId(), sent.getTimestamp(),
 				s.getInviteTimestamp());
 	}
 
-	private Message sendLeaveMessage(Transaction txn, Session session,
-			boolean visibleInUi) throws DbException {
+	private Message sendLeaveMessage(Transaction txn, Session session)
+			throws DbException {
 		Message m = messageEncoder.encodeLeaveMessage(
 				session.getContactGroupId(), session.getShareableId(),
 				getLocalTimestamp(session), session.getLastLocalMessageId());
-		sendMessage(txn, m, LEAVE, session.getShareableId(), visibleInUi);
+		sendMessage(txn, m, LEAVE, session.getShareableId(), false);
 		return m;
 	}
 
@@ -286,7 +294,10 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 			throws DbException, FormatException {
 		// The timestamp must be higher than the last invite message, if any
 		if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
-		// Mark the invite message visible in the UI and available to answer
+		// The dependency, if any, must be the last remote message
+		if (!isValidDependency(s, m.getPreviousMessageId()))
+			return abort(txn, s);
+		// Mark the invite message visible in the UI and (un)available to answer
 		markMessageVisibleInUi(txn, m.getId(), true);
 		markMessageAvailableToAnswer(txn, m.getId(), available);
 		// Track the message
@@ -309,14 +320,14 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		// The dependency, if any, must be the last remote message
 		if (!isValidDependency(s, m.getPreviousMessageId()))
 			return abort(txn, s);
-		// Mark the invite message visible in the UI and available to answer
+		// Mark the invite message visible in the UI and unavailable to answer
 		markMessageVisibleInUi(txn, m.getId(), true);
 		markMessageAvailableToAnswer(txn, m.getId(), false);
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
 				m.getTimestamp(), false);
 		// Share the shareable with the contact
-		setPrivateGroupVisibility(txn, s, SHARED);
+		setShareableVisibility(txn, s, SHARED);
 		// Broadcast an event
 		ContactId contactId = getContactId(txn, s.getContactGroupId());
 		txn.attach(
@@ -335,9 +346,9 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 			AcceptMessage m) throws DbException, FormatException {
 		switch (s.getState()) {
 			case REMOTE_INVITED:
-				return onRemoteAccept(txn, s, m);
+				return onRemoteAcceptWhenInvited(txn, s, m);
 			case REMOTE_HANGING:
-				return onRemoteAcceptWhenHanging(txn, s, m, LOCAL_LEFT);
+				return onRemoteAccept(txn, s, m, LOCAL_LEFT);
 			case START:
 			case LOCAL_INVITED:
 			case SHARING:
@@ -351,9 +362,8 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		}
 	}
 
-	private Session onRemoteAcceptWhenHanging(Transaction txn, Session s,
-			AcceptMessage m, State nextState)
-			throws DbException, FormatException {
+	private Session onRemoteAccept(Transaction txn, Session s, AcceptMessage m,
+			State nextState) throws DbException, FormatException {
 		// The timestamp must be higher than the last invite message
 		if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
 		// The dependency, if any, must be the last remote message
@@ -373,13 +383,13 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 				s.getInviteTimestamp());
 	}
 
-	private Session onRemoteAccept(Transaction txn, Session s, AcceptMessage m)
-			throws DbException, FormatException {
+	private Session onRemoteAcceptWhenInvited(Transaction txn, Session s,
+			AcceptMessage m) throws DbException, FormatException {
 		// Perform normal remote accept validation and operation
-		Session session = onRemoteAcceptWhenHanging(txn, s, m, SHARING);
+		Session session = onRemoteAccept(txn, s, m, SHARING);
 		// Share the shareable with the contact
 		if (session.getState() != ERROR)
-			setPrivateGroupVisibility(txn, s, SHARED);
+			setShareableVisibility(txn, s, SHARED);
 		return session;
 	}
 
@@ -392,7 +402,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		switch (s.getState()) {
 			case REMOTE_INVITED:
 			case REMOTE_HANGING:
-				return onRemoteDecline(txn, s, m, START);
+				return onRemoteDecline(txn, s, m);
 			case START:
 			case LOCAL_INVITED:
 			case SHARING:
@@ -407,8 +417,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 	}
 
 	private Session onRemoteDecline(Transaction txn, Session s,
-			DeclineMessage m, State nextState)
-			throws DbException, FormatException {
+			DeclineMessage m) throws DbException, FormatException {
 		// The timestamp must be higher than the last invite message
 		if (m.getTimestamp() <= s.getInviteTimestamp()) return abort(txn, s);
 		// The dependency, if any, must be the last remote message
@@ -419,11 +428,17 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
 				m.getTimestamp(), false);
+		// Make the shareable invisible (not actually needed in REMOTE_HANGING)
+		try {
+			setShareableVisibility(txn, s, INVISIBLE);
+		} catch (FormatException e) {
+			throw new DbException(e); // Invalid group metadata
+		}
 		// Broadcast an event
 		ContactId contactId = getContactId(txn, m.getContactGroupId());
 		txn.attach(getInvitationResponseReceivedEvent(m, contactId));
 		// Move to the next state
-		return new Session(nextState, s.getContactGroupId(), s.getShareableId(),
+		return new Session(START, s.getContactGroupId(), s.getShareableId(),
 				s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
 				s.getInviteTimestamp());
 	}
@@ -440,7 +455,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 			case LOCAL_LEFT:
 				return onRemoteLeave(txn, s, m, START);
 			case SHARING:
-				return onRemoteLeaveWhenSharing(txn, s, m, REMOTE_LEFT);
+				return onRemoteLeaveWhenSharing(txn, s, m);
 			case START:
 			case REMOTE_INVITED:
 			case REMOTE_LEFT:
@@ -478,13 +493,12 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 	}
 
 	private Session onRemoteLeaveWhenSharing(Transaction txn, Session s,
-			LeaveMessage m, State nextState)
-			throws DbException, FormatException {
+			LeaveMessage m) throws DbException, FormatException {
 		// Carry out normal leave validation and operation
-		Session session = onRemoteLeave(txn, s, m, nextState);
+		Session session = onRemoteLeave(txn, s, m, REMOTE_LEFT);
 		// Stop sharing the shareable with the contact
 		if (session.getState() != ERROR)
-			setPrivateGroupVisibility(txn, s, INVISIBLE);
+			setShareableVisibility(txn, s, INVISIBLE);
 		// Move to the next state
 		return session;
 	}
@@ -503,7 +517,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		markInvitesUnavailableToAnswer(txn, s);
 		// If we subscribe, make the shareable invisible to the contact
 		if (isSubscribed(txn, s.getShareableId()))
-			setPrivateGroupVisibility(txn, s, INVISIBLE);
+			setShareableVisibility(txn, s, INVISIBLE);
 		// Send an ABORT message
 		Message sent = sendAbortMessage(txn, s);
 		// Move to the ERROR state
@@ -526,9 +540,13 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 
 	private boolean isSubscribed(Transaction txn, GroupId g)
 			throws DbException {
-		return db.containsGroup(txn, g);
+		if (!db.containsGroup(txn, g)) return false;
+		Group group = db.getGroup(txn, g);
+		return group.getClientId().equals(getClientId());
 	}
 
+	protected abstract ClientId getClientId();
+
 	private Message sendAbortMessage(Transaction txn, Session session)
 			throws DbException {
 		Message m = messageEncoder.encodeAbortMessage(
@@ -572,7 +590,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		}
 	}
 
-	private void setPrivateGroupVisibility(Transaction txn, Session session,
+	private void setShareableVisibility(Transaction txn, Session session,
 			Visibility v) throws DbException, FormatException {
 		ContactId contactId = getContactId(txn, session.getContactGroupId());
 		db.setGroupVisibility(txn, contactId, session.getShareableId(), v);
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/Session.java b/briar-core/src/main/java/org/briarproject/briar/sharing/Session.java
index b1f826462d6ac8eec3ec098e2f33366c2bd79846..6e0a5520180d7b3ea984d8b098f324953e109ae0 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/Session.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/Session.java
@@ -32,8 +32,8 @@ class Session {
 		this.inviteTimestamp = inviteTimestamp;
 	}
 
-	Session(GroupId contactGroupId, GroupId privateGroupId) {
-		this(START, contactGroupId, privateGroupId, null, null, 0, 0);
+	Session(GroupId contactGroupId, GroupId shareableId) {
+		this(START, contactGroupId, shareableId, null, null, 0, 0);
 	}
 
 	public State getState() {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharerEngine.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharerEngine.java
index 5d959f40f7b5548dd65d4ebbc5ee6cc20257337b..eb05d87cdffc6b91aaa773bb7f5fc463a9d24f90 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharerEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharerEngine.java
@@ -38,12 +38,12 @@ class SharerEngine<I extends Invitation, SS extends SharerSessionState, IRR exte
 	private static final Logger LOG =
 			Logger.getLogger(SharerEngine.class.getName());
 
-	private final InvitationFactory<I, SS> invitationFactory;
+	private final OldInvitationFactory<I, SS> invitationFactory;
 	private final InvitationResponseReceivedEventFactory<SS, IRR>
 			invitationResponseReceivedEventFactory;
 	private final Clock clock;
 
-	SharerEngine(InvitationFactory<I, SS> invitationFactory,
+	SharerEngine(OldInvitationFactory<I, SS> invitationFactory,
 			InvitationResponseReceivedEventFactory<SS, IRR> invitationResponseReceivedEventFactory,
 			Clock clock) {
 		this.invitationFactory = invitationFactory;
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
index 32789212d226750bba202eac7bc7061f65fac924..e0c640e1e6258c1a8d4a905b14a3b840f1069a04 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
@@ -35,11 +35,9 @@ import org.briarproject.briar.client.ConversationClientImpl;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Set;
 
 import javax.annotation.Nullable;
 
@@ -62,18 +60,21 @@ abstract class SharingManagerImpl<S extends Shareable>
 	private final SessionParser sessionParser;
 	private final ContactGroupFactory contactGroupFactory;
 	private final ProtocolEngine<S> engine;
+	private final InvitationFactory<S> invitationFactory;
 
 	SharingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
 			MetadataParser metadataParser, MessageParser<S> messageParser,
 			SessionEncoder sessionEncoder, SessionParser sessionParser,
 			MessageTracker messageTracker,
-			ContactGroupFactory contactGroupFactory, ProtocolEngine<S> engine) {
+			ContactGroupFactory contactGroupFactory, ProtocolEngine<S> engine,
+			InvitationFactory<S> invitationFactory) {
 		super(db, clientHelper, metadataParser, messageTracker);
 		this.messageParser = messageParser;
 		this.sessionEncoder = sessionEncoder;
 		this.sessionParser = sessionParser;
 		this.contactGroupFactory = contactGroupFactory;
 		this.engine = engine;
+		this.invitationFactory = invitationFactory;
 	}
 
 	protected abstract ClientId getClientId();
@@ -137,8 +138,8 @@ abstract class SharingManagerImpl<S extends Shareable>
 		return false;
 	}
 
-	private SessionId getSessionId(GroupId privateGroupId) {
-		return new SessionId(privateGroupId.getBytes());
+	private SessionId getSessionId(GroupId shareableId) {
+		return new SessionId(shareableId.getBytes());
 	}
 
 	@Nullable
@@ -222,7 +223,7 @@ abstract class SharingManagerImpl<S extends Shareable>
 				session = new Session(contactGroupId, shareableId);
 				storageId = createStorageId(txn, contactGroupId);
 			} else {
-				// An earlier invite was declined, so we already have a session
+				// We already have a session
 				session = sessionParser
 						.parseSession(contactGroupId, ss.bdfSession);
 				storageId = ss.storageId;
@@ -315,26 +316,19 @@ abstract class SharingManagerImpl<S extends Shareable>
 			ContactId c, MessageId m, MessageMetadata meta,
 			MessageStatus status) throws DbException, FormatException {
 		// Look up the invite message to get the details of the private group
-		InviteMessage<S> invite = getInviteMessage(txn, m);
+		InviteMessage<S> invite = messageParser.getInviteMessage(txn, m);
 		boolean canBeOpened = db.containsGroup(txn, invite.getShareableId());
-		return engine.createInvitationRequest(meta.isLocal(), status.isSent(),
-				status.isSeen(), meta.isRead(), invite, c,
-				meta.isAvailableToAnswer(), canBeOpened);
-	}
-
-	private InviteMessage<S> getInviteMessage(Transaction txn, MessageId m)
-			throws DbException, FormatException {
-		Message message = clientHelper.getMessage(txn, m);
-		if (message == null) throw new DbException();
-		BdfList body = clientHelper.toList(message);
-		return messageParser.parseInviteMessage(message, body);
+		return invitationFactory
+				.createInvitationRequest(meta.isLocal(), status.isSent(),
+						status.isSeen(), meta.isRead(), invite, c,
+						meta.isAvailableToAnswer(), canBeOpened);
 	}
 
 	private InvitationResponse parseInvitationResponse(ContactId c,
 			GroupId contactGroupId, MessageId m, MessageMetadata meta,
 			MessageStatus status, boolean accept)
 			throws DbException, FormatException {
-		return engine.createInvitationResponse(m, contactGroupId,
+		return invitationFactory.createInvitationResponse(m, contactGroupId,
 				meta.getTimestamp(), meta.isLocal(), status.isSent(),
 				status.isSeen(), meta.isRead(), meta.getShareableId(), c,
 				accept);
@@ -346,9 +340,8 @@ abstract class SharingManagerImpl<S extends Shareable>
 		List<SharingInvitationItem> items =
 				new ArrayList<SharingInvitationItem>();
 		BdfDictionary query = messageParser.getInvitesAvailableToAnswerQuery();
-		Set<S> shareables = new HashSet<S>();
-		Map<GroupId, Collection<Contact>> sharers =
-				new HashMap<GroupId, Collection<Contact>>();
+		Map<S, Collection<Contact>> sharers =
+				new HashMap<S, Collection<Contact>>();
 		Transaction txn = db.startTransaction(true);
 		try {
 			// get invitations from each contact
@@ -358,21 +351,22 @@ abstract class SharingManagerImpl<S extends Shareable>
 						clientHelper.getMessageMetadataAsDictionary(txn,
 								contactGroupId, query);
 				for (MessageId m : results.keySet()) {
-					InviteMessage<S> invite = getInviteMessage(txn, m);
+					InviteMessage<S> invite =
+							messageParser.getInviteMessage(txn, m);
 					S s = invite.getShareable();
-					shareables.add(s);
-					if (sharers.containsKey(s.getId())) {
-						sharers.get(s.getId()).add(c);
+					if (sharers.containsKey(s)) {
+						sharers.get(s).add(c);
 					} else {
 						Collection<Contact> contacts = new ArrayList<Contact>();
 						contacts.add(c);
-						sharers.put(s.getId(), contacts);
+						sharers.put(s, contacts);
 					}
 				}
 			}
 			// construct the invitation items
-			for (S s : shareables) {
-				Collection<Contact> contacts = sharers.get(s.getId());
+			for (Entry<S, Collection<Contact>> e : sharers.entrySet()) {
+				S s = e.getKey();
+				Collection<Contact> contacts = e.getValue();
 				boolean subscribed = db.containsGroup(txn, s.getId());
 				SharingInvitationItem invitation =
 						new SharingInvitationItem(s, subscribed, contacts);
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
index 9eda631b6c719f4022e62db68bd85fa4237c718d..e4a24c1b73b1379b6eca6b36ef72e0a8c2c0570b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
@@ -129,4 +129,10 @@ public class SharingModule {
 		return forumProtocolEngine;
 	}
 
+	@Provides
+	InvitationFactory<Forum> provideForumInvitationFactory(
+			ForumInvitationFactoryImpl forumInvitationFactory) {
+		return forumInvitationFactory;
+	}
+
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingValidator.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingValidator.java
index 631a6b30c7817e9edb279bd510e085bcd312bb91..e8f9287f8f43162acd224d6ce0dc43dcc78c7fb3 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingValidator.java
@@ -59,13 +59,13 @@ abstract class SharingValidator extends BdfMessageValidator {
 		byte[] previousMessageId = body.getOptionalRaw(1);
 		checkLength(previousMessageId, UniqueId.LENGTH);
 		BdfList descriptor = body.getList(2);
-		GroupId groupId = validateDescriptor(descriptor);
+		GroupId shareableId = validateDescriptor(descriptor);
 		String msg = body.getOptionalString(3);
 		checkLength(msg, 1, MAX_INVITATION_MESSAGE_LENGTH);
 
 		BdfDictionary meta = messageEncoder
-				.encodeMetadata(INVITE, groupId, m.getTimestamp(), false, false,
-						false, false);
+				.encodeMetadata(INVITE, shareableId, m.getTimestamp(), false,
+						false, false, false);
 		if (previousMessageId == null) {
 			return new BdfMessageContext(meta);
 		} else {
@@ -81,14 +81,14 @@ abstract class SharingValidator extends BdfMessageValidator {
 	private BdfMessageContext validateNonInviteMessage(MessageType type,
 			Message m, BdfList body) throws FormatException {
 		checkSize(body, 3);
-		byte[] groupId = body.getRaw(1);
-		checkLength(groupId, UniqueId.LENGTH);
+		byte[] shareableId = body.getRaw(1);
+		checkLength(shareableId, UniqueId.LENGTH);
 		byte[] previousMessageId = body.getOptionalRaw(2);
 		checkLength(previousMessageId, UniqueId.LENGTH);
 
 		BdfDictionary meta = messageEncoder
-				.encodeMetadata(type, new GroupId(groupId), m.getTimestamp(),
-						false, false, false, false);
+				.encodeMetadata(type, new GroupId(shareableId),
+						m.getTimestamp(), false, false, false, false);
 		if (previousMessageId == null) {
 			return new BdfMessageContext(meta);
 		} else {
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java
index 774057c50a996454758a8faf30ce15c350bc3ab4..37c7b76e321a1e89a0d9ceb7c105499465dd5d31 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java
@@ -666,11 +666,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
 			oneOf(messageParser).parseMetadata(meta);
 			will(returnValue(messageMetadata1));
 			oneOf(db).getMessageStatus(txn, contactId, message.getId());
-			oneOf(clientHelper).getMessage(txn, message.getId());
-			will(returnValue(message));
-			oneOf(clientHelper).toList(message);
-			will(returnValue(body));
-			oneOf(messageParser).parseInviteMessage(message, body);
+			oneOf(messageParser).getInviteMessage(txn, message.getId());
 			will(returnValue(invite));
 			oneOf(privateGroupFactory).createPrivateGroup(invite.getGroupName(),
 					invite.getCreator(), invite.getSalt());
@@ -743,21 +739,13 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
 					contactGroup.getId(), query);
 			will(returnValue(results));
 			// message 1
-			oneOf(clientHelper).getMessage(txn, message.getId());
-			will(returnValue(message));
-			oneOf(clientHelper).toList(message);
-			will(returnValue(body));
-			oneOf(messageParser).parseInviteMessage(message, body);
+			oneOf(messageParser).getInviteMessage(txn, message.getId());
 			will(returnValue(inviteMessage1));
 			oneOf(privateGroupFactory).createPrivateGroup(groupName, author,
 					salt);
 			will(returnValue(pg));
 			// message 2
-			oneOf(clientHelper).getMessage(txn, messageId2);
-			will(returnValue(message2));
-			oneOf(clientHelper).toList(message2);
-			will(returnValue(body2));
-			oneOf(messageParser).parseInviteMessage(message2, body2);
+			oneOf(messageParser).getInviteMessage(txn, messageId2);
 			will(returnValue(inviteMessage2));
 			oneOf(privateGroupFactory).createPrivateGroup(groupName, author,
 					salt);
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
index 2e4c64ddb195b12e99cd89f3793cc0b417b61e1c..6e427277087c8550dfedfc14031106dd2b3f2ea4 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
@@ -144,11 +144,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
 		expectSendJoinMessage(properJoinMessage, true);
 		context.checking(new Expectations() {{
 			oneOf(messageTracker).trackOutgoingMessage(txn, message);
-			oneOf(clientHelper).getMessage(txn, lastRemoteMessageId);
-			will(returnValue(inviteMsg));
-			oneOf(clientHelper).toList(inviteMsg);
-			will(returnValue(inviteList));
-			oneOf(messageParser).parseInviteMessage(inviteMsg, inviteList);
+			oneOf(messageParser).getInviteMessage(txn, lastRemoteMessageId);
 			will(returnValue(inviteMessage));
 			oneOf(privateGroupFactory)
 					.createPrivateGroup(inviteMessage.getGroupName(),
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java
index 943baa034bf6225269b047824af970309a04ade8..3452025b9d3acacb709622b61b3971b2f3e82760 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingIntegrationTest.java
@@ -33,8 +33,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
-import javax.inject.Inject;
-
 import static junit.framework.Assert.assertNotNull;
 import static org.briarproject.bramble.test.TestUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
@@ -49,9 +47,6 @@ public class ForumSharingIntegrationTest
 	private InviteeListener listener1;
 	private Forum forum0;
 
-	@Inject
-	MessageEncoder messageEncoder;
-
 	// objects accessed from background threads need to be volatile
 	private volatile ForumSharingManager forumSharingManager0;
 	private volatile ForumSharingManager forumSharingManager1;
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
index 3772e87fc52eb281d29181351e8877dc70b14386..c7d2d98f26c03346953564585671a41d0f7f5c4a 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
@@ -3,6 +3,8 @@ package org.briarproject.briar.sharing;
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.UniqueId;
 import org.briarproject.bramble.api.client.BdfMessageContext;
+import org.briarproject.bramble.api.data.BdfDictionary;
+import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.test.TestUtils;
@@ -20,7 +22,6 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
 import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
 import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
 import static org.briarproject.briar.sharing.MessageType.ABORT;
 import static org.briarproject.briar.sharing.MessageType.ACCEPT;
 import static org.briarproject.briar.sharing.MessageType.DECLINE;
@@ -46,6 +47,8 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 	private final BdfList descriptor = BdfList.of(forumName, salt);
 	private final String content =
 			TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
+	private final BdfDictionary meta =
+			BdfDictionary.of(new BdfEntry("meta", "data"));
 
 	@Test
 	public void testAcceptsInvitationWithContent() throws Exception {
@@ -121,21 +124,20 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInvalidMessageType() throws Exception {
-		int invalidMessageType = SHARE_MSG_TYPE_ABORT + 1;
+		int invalidMessageType = ABORT.getValue() + 1;
 		v.validateMessage(message, group,
 				BdfList.of(invalidMessageType, groupId, previousMsgId));
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsNullGroupId() throws Exception {
+	public void testRejectsNullSessionId() throws Exception {
 		v.validateMessage(message, group,
 				BdfList.of(ABORT.getValue(), null, previousMsgId));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNonRawSessionId() throws Exception {
-		v.validateMessage(message, group,
-				BdfList.of(SHARE_MSG_TYPE_ABORT, 123));
+		v.validateMessage(message, group, BdfList.of(ABORT.getValue(), 123));
 	}
 
 	@Test(expected = FormatException.class)
@@ -161,13 +163,13 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongBodyForAbort() throws Exception {
 		v.validateMessage(message, group,
-				BdfList.of(SHARE_MSG_TYPE_ABORT, groupId, previousMsgId, 123));
+				BdfList.of(ABORT.getValue(), groupId, previousMsgId, 123));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortBodyForInvitation() throws Exception {
 		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), groupId, forumName));
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor));
 	}
 
 	@Test(expected = FormatException.class)
@@ -207,9 +209,10 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 		BdfList validDescriptor = BdfList.of(shortForumName, salt);
 		expectCreateForum(shortForumName);
 		expectEncodeMetadata(INVITE);
-		v.validateMessage(message, group,
+		BdfMessageContext messageContext = v.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, validDescriptor,
 						null));
+		assertExpectedContext(messageContext, previousMsgId);
 	}
 
 	@Test(expected = FormatException.class)
@@ -295,6 +298,7 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 			oneOf(messageEncoder)
 					.encodeMetadata(type, groupId, timestamp, false, false,
 							false, false);
+			will(returnValue(meta));
 		}});
 	}
 
@@ -307,6 +311,7 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 			assertEquals(1, dependencies.size());
 			assertTrue(dependencies.contains(previousMsgId));
 		}
+		assertEquals(meta, messageContext.getDictionary());
 	}
 
 }