diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java
index 2af7fb8e54522643319eb4215391879c310b2edf..67572ff2bd24bad82fb4853f6605c034e5066e06 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java
@@ -575,6 +575,10 @@ public class ConversationActivity extends BriarActivity
 			@Override
 			public void onSuccess(String contactName) {
 				runOnUiThreadUnlessDestroyed(() -> {
+					// If the other introducee declined, we can no longer
+					// respond to the request
+					if (!m.isIntroducer() && !m.wasAccepted())
+						markRequestAnswered(m.getSessionId());
 					ConversationItem item = ConversationItem
 							.from(ConversationActivity.this, contactName, m);
 					addConversationItem(item);
@@ -627,6 +631,26 @@ public class ConversationActivity extends BriarActivity
 		});
 	}
 
+	private void markRequestAnswered(SessionId sessionId) {
+		int size = adapter.getItemCount();
+		for (int i = 0; i < size; i++) {
+			ConversationItem item = adapter.getItemAt(i);
+			if (item instanceof ConversationRequestItem) {
+				ConversationRequestItem req = (ConversationRequestItem) item;
+				if (req.getSessionId().equals(sessionId)
+						&& !req.wasAnswered()) {
+					LOG.info("Marking request answered");
+					req.setAnswered(true);
+					int position = adapter.findItemPosition(req);
+					if (position != INVALID_POSITION)
+						adapter.notifyItemChanged(position, req);
+					// There shouldn't be more than one unanswered request
+					return;
+				}
+			}
+		}
+	}
+
 	private void markMessages(Collection<MessageId> messageIds,
 			boolean sent, boolean seen) {
 		runOnUiThreadUnlessDestroyed(() -> {
@@ -781,25 +805,18 @@ public class ConversationActivity extends BriarActivity
 				return;
 			}
 
-				PromptStateChangeListener listener = new PromptStateChangeListener() {
-					@Override
-					public void onPromptStateChanged(
-							MaterialTapTargetPrompt prompt, int state) {
-						if (state == STATE_DISMISSED ||
-					state == STATE_FINISHED) {
-introductionOnboardingSeen();
-					}
-					}
-
-				};
-				new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
-						R.style.OnboardingDialogTheme).setTarget(target)
-						.setPrimaryText(R.string.introduction_onboarding_title)
-						.setSecondaryText(R.string.introduction_onboarding_text)
-						.setIcon(R.drawable.ic_more_vert_accent)
-						.setPromptStateChangeListener(listener)
-						.show();
-
+			PromptStateChangeListener listener = (prompt, state) -> {
+				if (state == STATE_DISMISSED || state == STATE_FINISHED) {
+					introductionOnboardingSeen();
+				}
+			};
+			new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
+					R.style.OnboardingDialogTheme).setTarget(target)
+					.setPrimaryText(R.string.introduction_onboarding_title)
+					.setSecondaryText(R.string.introduction_onboarding_text)
+					.setIcon(R.drawable.ic_more_vert_accent)
+					.setPromptStateChangeListener(listener)
+					.show();
 		});
 	}
 
@@ -865,11 +882,12 @@ introductionOnboardingSeen();
 								"Unknown Request Type");
 				}
 				loadMessages();
+			} catch (ProtocolStateException e) {
+				// Action is no longer valid - reloading should solve the issue
+				if (LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
 			} catch (DbException e) {
-				// TODO use more generic error message
-				introductionResponseError();
-				if (LOG.isLoggable(WARNING))
-					LOG.log(WARNING, e.toString(), e);
+				// TODO show an error message
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
 		});
 	}
@@ -900,14 +918,8 @@ introductionOnboardingSeen();
 	@DatabaseExecutor
 	private void respondToIntroductionRequest(SessionId sessionId,
 			boolean accept, long time) throws DbException {
-		try {
-			introductionManager
-					.respondToIntroduction(contactId, sessionId, time, accept);
-		} catch (ProtocolStateException e) {
-			if (LOG.isLoggable(WARNING))
-				LOG.log(WARNING, e.toString(), e);
-			introductionResponseError();
-		}
+		introductionManager.respondToIntroduction(contactId, sessionId, time,
+				accept);
 	}
 
 	@DatabaseExecutor
@@ -925,18 +937,7 @@ introductionOnboardingSeen();
 	@DatabaseExecutor
 	private void respondToGroupRequest(SessionId id, boolean accept)
 			throws DbException {
-		try {
-			groupInvitationManager.respondToInvitation(contactId, id, accept);
-		} catch (ProtocolStateException e) {
-			// this action is no longer possible
-		}
-	}
-
-	private void introductionResponseError() {
-		runOnUiThreadUnlessDestroyed(() ->
-				Toast.makeText(ConversationActivity.this,
-						R.string.introduction_response_error,
-						Toast.LENGTH_SHORT).show());
+		groupInvitationManager.respondToInvitation(contactId, id, accept);
 	}
 
 	private ListenableFutureTask<String> getContactNameTask() {
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
index c9002bb0e61fb3f8f2989a1fdf308e52afa6858c..ae395b596928734020c2fa89c9afcaaad62174b1 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
@@ -84,8 +84,7 @@ abstract class AbstractProtocolEngine<S extends Session>
 	Message sendAcceptMessage(Transaction txn, PeerSession s, long timestamp,
 			byte[] ephemeralPublicKey, long acceptTimestamp,
 			Map<TransportId, TransportProperties> transportProperties,
-			boolean visible)
-			throws DbException {
+			boolean visible) throws DbException {
 		Message m = messageEncoder
 				.encodeAcceptMessage(s.getContactGroupId(), timestamp,
 						s.getLastLocalMessageId(), s.getSessionId(),
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
index c0cf65a073da10600635754295ab449a335c1f39..c1bab31a8accc2a2d5c1f18f56df070327ae0cae 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
@@ -357,9 +357,25 @@ class IntroduceeProtocolEngine
 		broadcastIntroductionResponseReceivedEvent(txn, s,
 				s.getIntroducer().getId(), m);
 
-		// Move back to START state
-		return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(),
-				s.getLocalTimestamp(), m.getMessageId());
+		if (s.getState() == AWAIT_RESPONSES) {
+			// Mark the request message unavailable to answer
+			markRequestsUnavailableToAnswer(txn, s);
+
+			// Send a DECLINE message
+			Message sent =
+					sendDeclineMessage(txn, s, getLocalTimestamp(s), false);
+
+			// Track the message
+			messageTracker.trackOutgoingMessage(txn, sent);
+
+			// Move back to START state
+			return IntroduceeSession.clear(s, START, sent.getId(),
+					sent.getTimestamp(), m.getMessageId());
+		} else {
+			// Move back to START state
+			return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(),
+					s.getLocalTimestamp(), m.getMessageId());
+		}
 	}
 
 	private IntroduceeSession onRemoteResponseWhenDeclined(Transaction txn,
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
index ed19fe651a5d1d0693426cd97fbaab89c031151e..50b9d01a949723be57fbc9baa7b64916e0762e6a 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
@@ -113,7 +113,7 @@ class IntroducerProtocolEngine
 				return onRemoteAccept(txn, s, m);
 			case A_DECLINED:
 			case B_DECLINED:
-				return onRemoteResponseWhenDeclined(txn, s, m);
+				return onRemoteAcceptWhenDeclined(txn, s, m);
 			case START:
 			case AWAIT_AUTHS:
 			case AWAIT_AUTH_A:
@@ -137,7 +137,7 @@ class IntroducerProtocolEngine
 				return onRemoteDecline(txn, s, m);
 			case A_DECLINED:
 			case B_DECLINED:
-				return onRemoteResponseWhenDeclined(txn, s, m);
+				return onRemoteDeclineWhenDeclined(txn, s, m);
 			case START:
 			case AWAIT_AUTHS:
 			case AWAIT_AUTH_A:
@@ -204,8 +204,8 @@ class IntroducerProtocolEngine
 	}
 
 	private IntroducerSession onLocalRequest(Transaction txn,
-			IntroducerSession s,
-			@Nullable String message, long timestamp) throws DbException {
+			IntroducerSession s, @Nullable String message, long timestamp)
+			throws DbException {
 		// Send REQUEST messages
 		long maxIntroduceeTimestamp =
 				Math.max(getLocalTimestamp(s, s.getIntroduceeA()),
@@ -285,6 +285,50 @@ class IntroducerProtocolEngine
 		return m.getGroupId().equals(s.getIntroduceeA().groupId);
 	}
 
+	private IntroducerSession onRemoteAcceptWhenDeclined(Transaction txn,
+			IntroducerSession s, AcceptMessage m) throws DbException {
+		// The timestamp must be higher than the last request message
+		if (m.getTimestamp() <= s.getRequestTimestamp())
+			return abort(txn, s);
+		// The dependency, if any, must be the last remote message
+		if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
+			return abort(txn, s);
+		// The message must be expected in the current state
+		boolean senderIsAlice = senderIsAlice(s, m);
+		if (senderIsAlice && s.getState() != B_DECLINED)
+			return abort(txn, s);
+		else if (!senderIsAlice && s.getState() != A_DECLINED)
+			return abort(txn, s);
+
+		// Mark the response visible in the UI
+		markMessageVisibleInUi(txn, m.getMessageId());
+		// Track the incoming message
+		messageTracker
+				.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
+
+		// Forward ACCEPT message
+		Introducee i = getOtherIntroducee(s, m.getGroupId());
+		Message sent = sendAcceptMessage(txn, i, getLocalTimestamp(s, i),
+				m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
+				m.getTransportProperties(), false);
+
+		Introducee introduceeA, introduceeB;
+		if (senderIsAlice) {
+			introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
+			introduceeB = new Introducee(s.getIntroduceeB(), sent);
+		} else {
+			introduceeA = new Introducee(s.getIntroduceeA(), sent);
+			introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
+		}
+
+		// Broadcast IntroductionResponseReceivedEvent
+		Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
+		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
+
+		return new IntroducerSession(s.getSessionId(), START,
+				s.getRequestTimestamp(), introduceeA, introduceeB);
+	}
+
 	private IntroducerSession onRemoteDecline(Transaction txn,
 			IntroducerSession s, DeclineMessage m) throws DbException {
 		// The timestamp must be higher than the last request message
@@ -334,9 +378,8 @@ class IntroducerProtocolEngine
 				s.getRequestTimestamp(), introduceeA, introduceeB);
 	}
 
-	private IntroducerSession onRemoteResponseWhenDeclined(Transaction txn,
-			IntroducerSession s, AbstractIntroductionMessage m)
-			throws DbException {
+	private IntroducerSession onRemoteDeclineWhenDeclined(Transaction txn,
+			IntroducerSession s, DeclineMessage m) throws DbException {
 		// The timestamp must be higher than the last request message
 		if (m.getTimestamp() <= s.getRequestTimestamp())
 			return abort(txn, s);
@@ -356,12 +399,17 @@ class IntroducerProtocolEngine
 		messageTracker
 				.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
 
+		// Forward DECLINE message
+		Introducee i = getOtherIntroducee(s, m.getGroupId());
+		long timestamp = getLocalTimestamp(s, i);
+		Message sent = sendDeclineMessage(txn, i, timestamp, false);
+
 		Introducee introduceeA, introduceeB;
 		if (senderIsAlice) {
 			introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
-			introduceeB = s.getIntroduceeB();
+			introduceeB = new Introducee(s.getIntroduceeB(), sent);
 		} else {
-			introduceeA = s.getIntroduceeA();
+			introduceeA = new Introducee(s.getIntroduceeA(), sent);
 			introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
 		}