diff --git a/src/api/endpoints/othello/match.ts b/src/api/endpoints/othello/match.ts
index 640be9cb57..b73e105ef0 100644
--- a/src/api/endpoints/othello/match.ts
+++ b/src/api/endpoints/othello/match.ts
@@ -2,7 +2,7 @@ import $ from 'cafy';
 import Matching, { pack as packMatching } from '../../models/othello-matching';
 import Game, { pack as packGame } from '../../models/othello-game';
 import User from '../../models/user';
-import { publishOthelloStream } from '../../event';
+import publishUserStream, { publishOthelloStream } from '../../event';
 import { eighteight } from '../../../common/othello/maps';
 
 module.exports = (params, user) => new Promise(async (res, rej) => {
@@ -48,6 +48,14 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
 		res(await packGame(game, user));
 
 		publishOthelloStream(exist.parent_id, 'matched', await packGame(game, exist.parent_id));
+
+		const other = await Matching.count({
+			child_id: user._id
+		});
+
+		if (other == 0) {
+			publishUserStream(user._id, 'othello_no_invites');
+		}
 	} else {
 		// Fetch child
 		const child = await User.findOne({
@@ -77,7 +85,11 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
 		// Reponse
 		res();
 
+		const packed = await packMatching(matching, child);
+
 		// 招待
-		publishOthelloStream(child._id, 'invited', await packMatching(matching, child));
+		publishOthelloStream(child._id, 'invited', packed);
+
+		publishUserStream(child._id, 'othello_invited', packed);
 	}
 });
diff --git a/src/api/models/othello-matching.ts b/src/api/models/othello-matching.ts
index 89fcd6df6a..5cc39cae13 100644
--- a/src/api/models/othello-matching.ts
+++ b/src/api/models/othello-matching.ts
@@ -32,6 +32,8 @@ export const pack = (
 
 	const _matching = deepcopy(matching);
 
+	// Rename _id to id
+	_matching.id = _matching._id;
 	delete _matching._id;
 
 	// Populate user
diff --git a/src/api/stream/othello.ts b/src/api/stream/othello.ts
index 5056eb535c..bd3b4a7637 100644
--- a/src/api/stream/othello.ts
+++ b/src/api/stream/othello.ts
@@ -1,5 +1,8 @@
+import * as mongo from 'mongodb';
 import * as websocket from 'websocket';
 import * as redis from 'redis';
+import Matching, { pack } from '../models/othello-matching';
+import publishUserStream from '../event';
 
 export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
 	// Subscribe othello stream
@@ -7,4 +10,20 @@ export default function(request: websocket.request, connection: websocket.connec
 	subscriber.on('message', (_, data) => {
 		connection.send(data);
 	});
+
+	connection.on('message', async (data) => {
+		const msg = JSON.parse(data.utf8Data);
+
+		switch (msg.type) {
+			case 'ping':
+				if (msg.id == null) return;
+				const matching = await Matching.findOne({
+					parent_id: user._id,
+					child_id: new mongo.ObjectID(msg.id)
+				});
+				if (matching == null) return;
+				publishUserStream(matching.child_id, 'othello_invited', await pack(matching, matching.child_id));
+				break;
+		}
+	});
 }
diff --git a/src/web/app/common/views/components/othello.vue b/src/web/app/common/views/components/othello.vue
index 81da02d1c4..d650322341 100644
--- a/src/web/app/common/views/components/othello.vue
+++ b/src/web/app/common/views/components/othello.vue
@@ -78,7 +78,8 @@ export default Vue.extend({
 			matching: null,
 			invitations: [],
 			connection: null,
-			connectionId: null
+			connectionId: null,
+			pingClock: null
 		};
 	},
 	watch: {
@@ -112,17 +113,29 @@ export default Vue.extend({
 		(this as any).api('othello/invitations').then(invitations => {
 			this.invitations = this.invitations.concat(invitations);
 		});
+
+		this.pingClock = setInterval(() => {
+			if (this.matching) {
+				this.connection.send({
+					type: 'ping',
+					id: this.matching.id
+				});
+			}
+		}, 3000);
 	},
 	beforeDestroy() {
 		this.connection.off('matched', this.onMatched);
 		this.connection.off('invited', this.onInvited);
 		(this as any).os.streams.othelloStream.dispose(this.connectionId);
+
+		clearInterval(this.pingClock);
 	},
 	methods: {
 		go(game) {
 			(this as any).api('othello/games/show', {
 				game_id: game.id
 			}).then(game => {
+				this.matching = null;
 				this.game = game;
 			});
 		},
@@ -154,11 +167,13 @@ export default Vue.extend({
 				user_id: invitation.parent.id
 			}).then(game => {
 				if (game) {
+					this.matching = null;
 					this.game = game;
 				}
 			});
 		},
 		onMatched(game) {
+			this.matching = null;
 			this.game = game;
 		},
 		onInvited(invite) {
diff --git a/src/web/app/desktop/views/components/ui.header.nav.vue b/src/web/app/desktop/views/components/ui.header.nav.vue
index 54045db8d4..7582e8afce 100644
--- a/src/web/app/desktop/views/components/ui.header.nav.vue
+++ b/src/web/app/desktop/views/components/ui.header.nav.vue
@@ -56,6 +56,8 @@ export default Vue.extend({
 
 			this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
 			this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.on('othello_invited', this.onOthelloInvited);
+			this.connection.on('othello_no_invites', this.onOthelloNoInvites);
 
 			// Fetch count of unread messaging messages
 			(this as any).api('messaging/unread').then(res => {
@@ -69,16 +71,26 @@ export default Vue.extend({
 		if ((this as any).os.isSignedIn) {
 			this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
 			this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.off('othello_invited', this.onOthelloInvited);
+			this.connection.off('othello_no_invites', this.onOthelloNoInvites);
 			(this as any).os.stream.dispose(this.connectionId);
 		}
 	},
 	methods: {
+		onUnreadMessagingMessage() {
+			this.hasUnreadMessagingMessages = true;
+		},
+
 		onReadAllMessagingMessages() {
 			this.hasUnreadMessagingMessages = false;
 		},
 
-		onUnreadMessagingMessage() {
-			this.hasUnreadMessagingMessages = true;
+		onOthelloInvited() {
+			this.hasGameInvitations = true;
+		},
+
+		onOthelloNoInvites() {
+			this.hasGameInvitations = false;
 		},
 
 		messaging() {
diff --git a/src/web/app/mobile/views/components/ui.header.vue b/src/web/app/mobile/views/components/ui.header.vue
index f06a35fe76..1ccbd5c951 100644
--- a/src/web/app/mobile/views/components/ui.header.vue
+++ b/src/web/app/mobile/views/components/ui.header.vue
@@ -6,7 +6,7 @@
 		<p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ os.i.name }}</b>さん</p>
 		<div class="content" ref="mainContainer">
 			<button class="nav" @click="$parent.isDrawerOpening = true">%fa:bars%</button>
-			<template v-if="hasUnreadNotifications || hasUnreadMessagingMessages">%fa:circle%</template>
+			<template v-if="hasUnreadNotifications || hasUnreadMessagingMessages || hasGameInvitations">%fa:circle%</template>
 			<h1>
 				<slot>Misskey</slot>
 			</h1>
@@ -26,6 +26,7 @@ export default Vue.extend({
 		return {
 			hasUnreadNotifications: false,
 			hasUnreadMessagingMessages: false,
+			hasGameInvitations: false,
 			connection: null,
 			connectionId: null
 		};
@@ -39,6 +40,8 @@ export default Vue.extend({
 			this.connection.on('unread_notification', this.onUnreadNotification);
 			this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
 			this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.on('othello_invited', this.onOthelloInvited);
+			this.connection.on('othello_no_invites', this.onOthelloNoInvites);
 
 			// Fetch count of unread notifications
 			(this as any).api('notifications/get_unread_count').then(res => {
@@ -107,6 +110,8 @@ export default Vue.extend({
 			this.connection.off('unread_notification', this.onUnreadNotification);
 			this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
 			this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.off('othello_invited', this.onOthelloInvited);
+			this.connection.off('othello_no_invites', this.onOthelloNoInvites);
 			(this as any).os.stream.dispose(this.connectionId);
 		}
 	},
@@ -122,6 +127,12 @@ export default Vue.extend({
 		},
 		onUnreadMessagingMessage() {
 			this.hasUnreadMessagingMessages = true;
+		},
+		onOthelloInvited() {
+			this.hasGameInvitations = true;
+		},
+		onOthelloNoInvites() {
+			this.hasGameInvitations = false;
 		}
 	}
 });
diff --git a/src/web/app/mobile/views/components/ui.nav.vue b/src/web/app/mobile/views/components/ui.nav.vue
index ba35a2783d..b8bc2fb040 100644
--- a/src/web/app/mobile/views/components/ui.nav.vue
+++ b/src/web/app/mobile/views/components/ui.nav.vue
@@ -18,7 +18,7 @@
 					<li><router-link to="/">%fa:home%%i18n:mobile.tags.mk-ui-nav.home%%fa:angle-right%</router-link></li>
 					<li><router-link to="/i/notifications">%fa:R bell%%i18n:mobile.tags.mk-ui-nav.notifications%<template v-if="hasUnreadNotifications">%fa:circle%</template>%fa:angle-right%</router-link></li>
 					<li><router-link to="/i/messaging">%fa:R comments%%i18n:mobile.tags.mk-ui-nav.messaging%<template v-if="hasUnreadMessagingMessages">%fa:circle%</template>%fa:angle-right%</router-link></li>
-					<li><router-link to="/othello">%fa:gamepad%ゲーム%fa:angle-right%</router-link></li>
+					<li><router-link to="/othello">%fa:gamepad%ゲーム<template v-if="hasGameInvitations">%fa:circle%</template>%fa:angle-right%</router-link></li>
 				</ul>
 				<ul>
 					<li><a :href="chUrl" target="_blank">%fa:tv%%i18n:mobile.tags.mk-ui-nav.ch%%fa:angle-right%</a></li>
@@ -47,6 +47,7 @@ export default Vue.extend({
 		return {
 			hasUnreadNotifications: false,
 			hasUnreadMessagingMessages: false,
+			hasGameInvitations: false,
 			connection: null,
 			connectionId: null,
 			aboutUrl: `${docsUrl}/${lang}/about`,
@@ -62,6 +63,8 @@ export default Vue.extend({
 			this.connection.on('unread_notification', this.onUnreadNotification);
 			this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
 			this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.on('othello_invited', this.onOthelloInvited);
+			this.connection.on('othello_no_invites', this.onOthelloNoInvites);
 
 			// Fetch count of unread notifications
 			(this as any).api('notifications/get_unread_count').then(res => {
@@ -84,6 +87,8 @@ export default Vue.extend({
 			this.connection.off('unread_notification', this.onUnreadNotification);
 			this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
 			this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.off('othello_invited', this.onOthelloInvited);
+			this.connection.off('othello_no_invites', this.onOthelloNoInvites);
 			(this as any).os.stream.dispose(this.connectionId);
 		}
 	},
@@ -104,6 +109,12 @@ export default Vue.extend({
 		},
 		onUnreadMessagingMessage() {
 			this.hasUnreadMessagingMessages = true;
+		},
+		onOthelloInvited() {
+			this.hasGameInvitations = true;
+		},
+		onOthelloNoInvites() {
+			this.hasGameInvitations = false;
 		}
 	}
 });