diff --git a/frontend/lib/features/room/game_room.dart b/frontend/lib/features/room/game_room.dart
index 53d98d5..78c9a91 100644
--- a/frontend/lib/features/room/game_room.dart
+++ b/frontend/lib/features/room/game_room.dart
@@ -1,6 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:frontend/providers/auth.dart';
+import 'package:frontend/providers/game_messages.dart';
 import 'package:go_router/go_router.dart';
 import 'package:logging/logging.dart';
 
@@ -18,25 +19,19 @@ class GameRoomHome extends ConsumerStatefulWidget {
 class _GameRoomHomeState extends ConsumerState<GameRoomHome> {
   @override
   Widget build(BuildContext context) {
-    final jwtAsync = ref.watch(jwtNotifierProvider);
-    return Scaffold(
-      body: jwtAsync.when(
-        data: (jwt) {
-          if (jwt == null || jwt.roomUuid != widget.roomUuid) {
-            logger.fine('Tried to open room, but not authenticated / wrong room');
-            // return home
-            context.go('/');
-          }
-          return Column(
-            children: [
-              Text('Authenticated.'),
-              Text('Welcome to room ${widget.roomUuid}'),
-            ],
-          );
-        },
-        loading: () => CircularProgressIndicator(),
-        error: (e, st) => Text('$e, $st'),
-      ),
+    final jwt = ref.watch(jwtBodyProvider);
+    if (jwt == null || jwt.roomUuid != widget.roomUuid) {
+      logger.fine('Tried to open room, but not authenticated / wrong room');
+      // return home
+      context.go('/');
+    }
+    // enstablish ws connection at /room/roomCode/ws and save to gameMessageProvider
+    ref.read(gameMessageNotifierProvider.notifier).connect(jwt!.roomUuid);
+    return Column(
+      children: [
+        Text('Authenticated.'),
+        Text('Welcome to room ${widget.roomUuid}'),
+      ],
     );
   }
 }
diff --git a/frontend/lib/providers/auth.dart b/frontend/lib/providers/auth.dart
index 1646e35..1875d9e 100644
--- a/frontend/lib/providers/auth.dart
+++ b/frontend/lib/providers/auth.dart
@@ -1,3 +1,4 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:jwt_decoder/jwt_decoder.dart';
 import 'package:logging/logging.dart';
 import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -11,7 +12,7 @@ final logger = Logger('provider/auth');
 @riverpod
 class JwtNotifier extends _$JwtNotifier {
   @override
-  Future<JWTBody?> build() async {
+  Future<String?> build() async {
     if (!await SharedPreferencesAsync().containsKey('jwt')) {
       logger.fine('No JWT saved to client');
       return null;
@@ -23,20 +24,7 @@ class JwtNotifier extends _$JwtNotifier {
       return null;
     }
 
-    final payload = JwtDecoder.tryDecode(jwtString);
-    if (payload == null) {
-      logger.fine('Failed to decode JWT, removing key.');
-      SharedPreferencesAsync().remove('jwt');
-      return null;
-    }
-
-    try {
-      final body = JWTBody.fromJson(payload);
-      return body;
-    } catch (e) {
-      logger.shout('Failed to parse JWT payload to JWTBody, something is wrong:', e, StackTrace.current);
-      return null;
-    }
+    return jwtString;
   }
 
   Future<void> setJwt(String jwt) async {
@@ -50,11 +38,30 @@ class JwtNotifier extends _$JwtNotifier {
     logger.fine('Saving jwt token to shared prefs');
     await SharedPreferencesAsync().setString('jwt', jwt);
 
-    try {
-      final jwtBody = JWTBody.fromJson(payload);
-      state = AsyncValue.data(jwtBody);
-    } catch (e) {
-      state = AsyncError(e, StackTrace.current);
-    }
+    state = AsyncValue.data(jwt);
+  }
+}
+
+@riverpod
+JWTBody? jwtBody(Ref ref) {
+  final jwtString = ref.watch(jwtNotifierProvider).valueOrNull;
+  if (jwtString == null) {
+    return null;
+  }
+
+  final payload = JwtDecoder.tryDecode(jwtString);
+  if (payload == null) {
+    logger.fine('Failed to decode JWT, removing key.');
+    SharedPreferencesAsync().remove('jwt');
+    ref.invalidate(jwtNotifierProvider);
+    return null;
+  }
+  try {
+    final body = JWTBody.fromJson(payload);
+    return body;
+  } catch (e) {
+    logger.shout(
+        'Failed to parse JWT payload to JWTBody, something is wrong.\nPayload: $payload', e, StackTrace.current);
+    return null;
   }
 }
diff --git a/frontend/lib/providers/dio.dart b/frontend/lib/providers/dio.dart
index edd4dc4..9cc0dda 100644
--- a/frontend/lib/providers/dio.dart
+++ b/frontend/lib/providers/dio.dart
@@ -1,5 +1,6 @@
 import 'package:dio/dio.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:frontend/providers/auth.dart';
 import 'package:logging/logging.dart';
 import 'package:riverpod_annotation/riverpod_annotation.dart';
 
@@ -15,11 +16,30 @@ Dio dio(Ref ref) {
     receiveTimeout: const Duration(seconds: 3),
   ));
 
-  dio.interceptors.add(LogInterceptor(responseBody: true));
+  final jwt = ref.watch(jwtNotifierProvider).valueOrNull;
+
+  dio.interceptors.addAll([JwtInterceptor(jwt: jwt), CustomLogInterceptor()]);
+
+  logger.fine('Created new Dio object');
 
   return dio;
 }
 
+// Adds the jwt to
+class JwtInterceptor extends Interceptor {
+  final String? jwt;
+
+  JwtInterceptor({required this.jwt});
+
+  @override
+  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
+    if (jwt != null) {
+      options.headers['Authorization'] = 'Bearer $jwt';
+    }
+    handler.next(options);
+  }
+}
+
 // Create a custom LogInterceptor using the logger object
 class CustomLogInterceptor extends Interceptor {
   @override
diff --git a/frontend/lib/providers/game_messages.dart b/frontend/lib/providers/game_messages.dart
new file mode 100644
index 0000000..29c6adf
--- /dev/null
+++ b/frontend/lib/providers/game_messages.dart
@@ -0,0 +1,43 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:frontend/providers/dio.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:shared_models/room.dart';
+
+part 'game_messages.g.dart';
+
+@riverpod
+class GameMessageNotifier extends _$GameMessageNotifier {
+  @override
+  Stream<GameRoomMessage> build() {
+    return Stream.empty();
+  }
+
+  Future<void> connect(String gameRoomUuid) async {
+    final dio = ref.read(dioProvider);
+    Uri.parse('ws://localhost:8080/room/$gameRoomUuid/ws');
+
+    // connect to websocket and then set stream of websocket to state
+    final wsUrl = Uri.parse('ws://localhost:8080/room/$gameRoomUuid/ws');
+
+    final connection = await WebSocket.connect(wsUrl.toString());
+
+    final Stream<GameRoomMessage> gameRoomStream = connection.map((message) {
+      try {
+        if (message is String) {
+          GameRoomMessage.fromJson(jsonDecode(message) as Map<String, dynamic>);
+        } else {
+          logger.info('Recieved non-string message in socket: $message');
+        }
+      } catch (e) {
+        print('Error parsing message: $e');
+        rethrow;
+      }
+    });
+    gameRoomStream.listen(
+      (event) => state = AsyncValue.data(event),
+    );
+  }
+}
diff --git a/shared_models/lib/room.dart b/shared_models/lib/room.dart
index 9fbb011..aade6ae 100644
--- a/shared_models/lib/room.dart
+++ b/shared_models/lib/room.dart
@@ -23,3 +23,51 @@ class CreateRoomResponse {
 
   Map<String, dynamic> toJson() => _$CreateRoomResponseToJson(this);
 }
+
+sealed class GameRoomMessage {
+  const GameRoomMessage();
+
+  factory GameRoomMessage.fromJson(Map<String, dynamic> json) {
+    return switch (json['type']) {
+      'ping' => PingMessage.fromJson(json),
+      'playerVote' => PlayerVoteMessage.fromJson(json),
+      _ => throw Exception('Unknown message type'),
+    };
+  }
+
+  Map<String, dynamic> toJson();
+}
+
+class PingMessage extends GameRoomMessage {
+  final DateTime timestamp;
+
+  const PingMessage({required this.timestamp});
+
+  factory PingMessage.fromJson(Map<String, dynamic> json) =>
+      PingMessage(timestamp: DateTime.parse(json['timestamp'] as String));
+
+  @override
+  Map<String, dynamic> toJson() => {
+        'type': 'ping',
+        'timestamp': timestamp.toIso8601String(),
+      };
+}
+
+class PlayerVoteMessage extends GameRoomMessage {
+  final String playerUuid;
+  final int vote;
+
+  const PlayerVoteMessage({required this.playerUuid, required this.vote});
+
+  factory PlayerVoteMessage.fromJson(Map<String, dynamic> json) => PlayerVoteMessage(
+        playerUuid: json['playerUuid'] as String,
+        vote: json['vote'] as int,
+      );
+
+  @override
+  Map<String, dynamic> toJson() => {
+        'type': 'playerVote',
+        'playerUuid': playerUuid,
+        'vote': vote,
+      };
+}