diff --git a/spec/unit/embedded.spec.ts b/spec/unit/embedded.spec.ts
index 5e35c02c9696bcafc2fbaea198b25f24d4f644e8..c5ef3a6a2c6d1dea005a43a5943e7a8708382883 100644
--- a/spec/unit/embedded.spec.ts
+++ b/spec/unit/embedded.spec.ts
@@ -28,6 +28,7 @@ import {
     WidgetApiToWidgetAction,
     MatrixCapabilities,
     ITurnServer,
+    IRoomEvent,
     IOpenIDCredentials,
     ISendEventFromWidgetResponseData,
     WidgetApiResponseError,
@@ -634,20 +635,12 @@ describe("RoomWidgetClient", () => {
         });
 
         it("receives", async () => {
-            const init = makeClient({ receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
+            await makeClient({ receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
             expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
             expect(widgetApi.requestCapabilityToReceiveState).toHaveBeenCalledWith("org.example.foo", "bar");
-            // Client needs to be told that the room state is loaded
-            widgetApi.emit(
-                `action:${WidgetApiToWidgetAction.UpdateState}`,
-                new CustomEvent(`action:${WidgetApiToWidgetAction.UpdateState}`, { detail: { data: { state: [] } } }),
-            );
-            await init;
 
             const emittedEvent = new Promise<MatrixEvent>((resolve) => client.once(ClientEvent.Event, resolve));
             const emittedSync = new Promise<SyncState>((resolve) => client.once(ClientEvent.Sync, resolve));
-            // Let's assume that a state event comes in but it doesn't actually
-            // update the state of the room just yet (maybe it's unauthorized)
             widgetApi.emit(
                 `action:${WidgetApiToWidgetAction.SendEvent}`,
                 new CustomEvent(`action:${WidgetApiToWidgetAction.SendEvent}`, { detail: { data: event } }),
@@ -656,43 +649,26 @@ describe("RoomWidgetClient", () => {
             // The client should've emitted about the received event
             expect((await emittedEvent).getEffectiveEvent()).toEqual(event);
             expect(await emittedSync).toEqual(SyncState.Syncing);
-            // However it should not have changed the room state
+            // It should've also inserted the event into the room object
             const room = client.getRoom("!1:example.org");
-            expect(room!.currentState.getStateEvents("org.example.foo", "bar")).toBe(null);
-
-            // Now assume that the state event becomes favored by state
-            // resolution for whatever reason and enters into the current state
-            // of the room
-            widgetApi.emit(
-                `action:${WidgetApiToWidgetAction.UpdateState}`,
-                new CustomEvent(`action:${WidgetApiToWidgetAction.UpdateState}`, {
-                    detail: { data: { state: [event] } },
-                }),
-            );
-            // It should now have changed the room state
+            expect(room).not.toBeNull();
             expect(room!.currentState.getStateEvents("org.example.foo", "bar")?.getEffectiveEvent()).toEqual(event);
         });
 
-        it("ignores state updates for other rooms", async () => {
-            const init = makeClient({ receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
-            // Client needs to be told that the room state is loaded
-            widgetApi.emit(
-                `action:${WidgetApiToWidgetAction.UpdateState}`,
-                new CustomEvent(`action:${WidgetApiToWidgetAction.UpdateState}`, { detail: { data: { state: [] } } }),
+        it("backfills", async () => {
+            widgetApi.readStateEvents.mockImplementation(async (eventType, limit, stateKey) =>
+                eventType === "org.example.foo" && (limit ?? Infinity) > 0 && stateKey === "bar"
+                    ? [event as IRoomEvent]
+                    : [],
             );
-            await init;
 
-            // Now a room we're not interested in receives a state update
-            widgetApi.emit(
-                `action:${WidgetApiToWidgetAction.UpdateState}`,
-                new CustomEvent(`action:${WidgetApiToWidgetAction.UpdateState}`, {
-                    detail: { data: { state: [{ ...event, room_id: "!other-room:example.org" }] } },
-                }),
-            );
-            // No change to the room state
-            for (const room of client.getRooms()) {
-                expect(room.currentState.getStateEvents("org.example.foo", "bar")).toBe(null);
-            }
+            await makeClient({ receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
+            expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
+            expect(widgetApi.requestCapabilityToReceiveState).toHaveBeenCalledWith("org.example.foo", "bar");
+
+            const room = client.getRoom("!1:example.org");
+            expect(room).not.toBeNull();
+            expect(room!.currentState.getStateEvents("org.example.foo", "bar")?.getEffectiveEvent()).toEqual(event);
         });
     });
 
diff --git a/src/embedded.ts b/src/embedded.ts
index 53154e40e620a19df5c397b7f2288c295595169e..b0cc4c158e89d9fef2c56e458401a08ae70d940b 100644
--- a/src/embedded.ts
+++ b/src/embedded.ts
@@ -28,7 +28,6 @@ import {
     WidgetApiAction,
     IWidgetApiResponse,
     IWidgetApiResponseData,
-    IUpdateStateToWidgetActionRequest,
 } from "matrix-widget-api";
 
 import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event.ts";
@@ -137,7 +136,6 @@ export type EventHandlerMap = { [RoomWidgetClientEvent.PendingEventsChanged]: ()
 export class RoomWidgetClient extends MatrixClient {
     private room?: Room;
     private readonly widgetApiReady: Promise<void>;
-    private readonly roomStateSynced: Promise<void>;
     private lifecycle?: AbortController;
     private syncState: SyncState | null = null;
 
@@ -191,11 +189,6 @@ export class RoomWidgetClient extends MatrixClient {
         };
 
         this.widgetApiReady = new Promise<void>((resolve) => this.widgetApi.once("ready", resolve));
-        this.roomStateSynced = capabilities.receiveState?.length
-            ? new Promise<void>((resolve) =>
-                  this.widgetApi.once(`action:${WidgetApiToWidgetAction.UpdateState}`, resolve),
-              )
-            : Promise.resolve();
 
         // Request capabilities for the functionality this client needs to support
         if (
@@ -248,7 +241,6 @@ export class RoomWidgetClient extends MatrixClient {
 
         widgetApi.on(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
         widgetApi.on(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
-        widgetApi.on(`action:${WidgetApiToWidgetAction.UpdateState}`, this.onStateUpdate);
 
         // Open communication with the host
         widgetApi.start();
@@ -284,6 +276,28 @@ export class RoomWidgetClient extends MatrixClient {
 
         await this.widgetApiReady;
 
+        // Backfill the requested events
+        // We only get the most recent event for every type + state key combo,
+        // so it doesn't really matter what order we inject them in
+        await Promise.all(
+            this.capabilities.receiveState?.map(async ({ eventType, stateKey }) => {
+                const rawEvents = await this.widgetApi.readStateEvents(eventType, undefined, stateKey, [this.roomId]);
+                const events = rawEvents.map((rawEvent) => new MatrixEvent(rawEvent as Partial<IEvent>));
+
+                if (this.syncApi instanceof SyncApi) {
+                    // Passing undefined for `stateAfterEventList` allows will make `injectRoomEvents` run in legacy mode
+                    // -> state events in `timelineEventList` will update the state.
+                    await this.syncApi.injectRoomEvents(this.room!, undefined, events);
+                } else {
+                    await this.syncApi!.injectRoomEvents(this.room!, events); // Sliding Sync
+                }
+                events.forEach((event) => {
+                    this.emit(ClientEvent.Event, event);
+                    logger.info(`Backfilled event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
+                });
+            }) ?? [],
+        );
+
         if (opts.clientWellKnownPollPeriod !== undefined) {
             this.clientWellKnownIntervalID = setInterval(() => {
                 this.fetchClientWellKnown();
@@ -291,9 +305,8 @@ export class RoomWidgetClient extends MatrixClient {
             this.fetchClientWellKnown();
         }
 
-        await this.roomStateSynced;
         this.setSyncState(SyncState.Syncing);
-        logger.info("Finished initial sync");
+        logger.info("Finished backfilling events");
 
         this.matrixRTC.start();
 
@@ -304,7 +317,6 @@ export class RoomWidgetClient extends MatrixClient {
     public stopClient(): void {
         this.widgetApi.off(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
         this.widgetApi.off(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
-        this.widgetApi.off(`action:${WidgetApiToWidgetAction.UpdateState}`, this.onStateUpdate);
 
         super.stopClient();
         this.lifecycle!.abort(); // Signal to other async tasks that the client has stopped
@@ -562,15 +574,36 @@ export class RoomWidgetClient extends MatrixClient {
             // Only inject once we have update the txId
             await this.updateTxId(event);
 
+            // The widget API does not tell us whether a state event came from `state_after` or not so we assume legacy behaviour for now.
             if (this.syncApi instanceof SyncApi) {
-                await this.syncApi.injectRoomEvents(this.room!, undefined, [], [event]);
+                // The code will want to be something like:
+                // ```
+                // if (!params.addToTimeline && !params.addToState) {
+                // // Passing undefined for `stateAfterEventList` makes `injectRoomEvents` run in "legacy mode"
+                // // -> state events part of the `timelineEventList` parameter will update the state.
+                //     this.injectRoomEvents(this.room!, [], undefined, [event]);
+                // } else {
+                //     this.injectRoomEvents(this.room!, undefined, params.addToState ? [event] : [], params.addToTimeline ? [event] : []);
+                // }
+                // ```
+
+                // Passing undefined for `stateAfterEventList` allows will make `injectRoomEvents` run in legacy mode
+                // -> state events in `timelineEventList` will update the state.
+                await this.syncApi.injectRoomEvents(this.room!, [], undefined, [event]);
             } else {
-                // Sliding Sync
-                await this.syncApi!.injectRoomEvents(this.room!, [], [event]);
+                // The code will want to be something like:
+                // ```
+                // if (!params.addToTimeline && !params.addToState) {
+                //     this.injectRoomEvents(this.room!, [], [event]);
+                // } else {
+                //     this.injectRoomEvents(this.room!, params.addToState ? [event] : [], params.addToTimeline ? [event] : []);
+                // }
+                // ```
+                await this.syncApi!.injectRoomEvents(this.room!, [], [event]); // Sliding Sync
             }
             this.emit(ClientEvent.Event, event);
             this.setSyncState(SyncState.Syncing);
-            logger.info(`Received event ${event.getId()} ${event.getType()}`);
+            logger.info(`Received event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
         } else {
             const { event_id: eventId, room_id: roomId } = ev.detail.data;
             logger.info(`Received event ${eventId} for a different room ${roomId}; discarding`);
@@ -595,32 +628,6 @@ export class RoomWidgetClient extends MatrixClient {
         await this.ack(ev);
     };
 
-    private onStateUpdate = async (ev: CustomEvent<IUpdateStateToWidgetActionRequest>): Promise<void> => {
-        ev.preventDefault();
-
-        for (const rawEvent of ev.detail.data.state) {
-            // Verify the room ID matches, since it's possible for the client to
-            // send us state updates from other rooms if this widget is always
-            // on screen
-            if (rawEvent.room_id === this.roomId) {
-                const event = new MatrixEvent(rawEvent as Partial<IEvent>);
-
-                if (this.syncApi instanceof SyncApi) {
-                    await this.syncApi.injectRoomEvents(this.room!, undefined, [event]);
-                } else {
-                    // Sliding Sync
-                    await this.syncApi!.injectRoomEvents(this.room!, [event]);
-                }
-                logger.info(`Updated state entry ${event.getType()} ${event.getStateKey()} to ${event.getId()}`);
-            } else {
-                const { event_id: eventId, room_id: roomId } = ev.detail.data;
-                logger.info(`Received state entry ${eventId} for a different room ${roomId}; discarding`);
-            }
-        }
-
-        await this.ack(ev);
-    };
-
     private async watchTurnServers(): Promise<void> {
         const servers = this.widgetApi.getTurnServers();
         const onClientStopped = (): void => {
diff --git a/yarn.lock b/yarn.lock
index 6df971ed325362e8de5a220aa1dd2c7e470af30d..c02d9f3744c1bbb49635d0f6d5b1024026571fbd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4875,9 +4875,9 @@ matrix-mock-request@^2.5.0:
     expect "^28.1.0"
 
 matrix-widget-api@^1.10.0:
-  version "1.12.0"
-  resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.12.0.tgz#b3d22bab1670051c8eeee66bb96d08b33148bc99"
-  integrity sha512-6JRd9fJGGvuBRhcTg9wX+Skn/Q1wox3jdp5yYQKJ6pPw4urW9bkTR90APBKVDB1vorJKT44jml+lCzkDMRBjww==
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.10.0.tgz#d31ea073a5871a1fb1a511ef900b0c125a37bf55"
+  integrity sha512-rkAJ29briYV7TJnfBVLVSKtpeBrBju15JZFSDP6wj8YdbCu1bdmlplJayQ+vYaw1x4fzI49Q+Nz3E85s46sRDw==
   dependencies:
     "@types/events" "^3.0.0"
     events "^3.2.0"