diff --git a/spec/unit/embedded.spec.ts b/spec/unit/embedded.spec.ts
index 6da1b7c6e8dbcd375b126db3aa25a0a33062d77e..fac2c9958eb8595cb57d3860e59a8644f2f0b792 100644
--- a/spec/unit/embedded.spec.ts
+++ b/spec/unit/embedded.spec.ts
@@ -31,6 +31,9 @@ import {
     type IOpenIDCredentials,
     type ISendEventFromWidgetResponseData,
     WidgetApiResponseError,
+    UnstableApiVersion,
+    type ApiVersion,
+    type IRoomEvent,
 } from "matrix-widget-api";
 
 import { createRoomWidgetClient, MatrixError, MsgType, UpdateDelayedEventAction } from "../../src/matrix";
@@ -40,6 +43,9 @@ import { type ICapabilities, type RoomWidgetClient } from "../../src/embedded";
 import { MatrixEvent } from "../../src/models/event";
 import { type ToDeviceBatch } from "../../src/models/ToDeviceMessage";
 import { sleep } from "../../src/utils";
+import { SlidingSync } from "../../src/sliding-sync";
+import { logger } from "../../src/logger";
+import { flushPromises } from "../test-utils/flushPromises";
 
 const testOIDCToken = {
     access_token: "12345678",
@@ -49,6 +55,7 @@ const testOIDCToken = {
 };
 class MockWidgetApi extends EventEmitter {
     public start = jest.fn().mockResolvedValue(undefined);
+    public getClientVersions = jest.fn();
     public requestCapability = jest.fn().mockResolvedValue(undefined);
     public requestCapabilities = jest.fn().mockResolvedValue(undefined);
     public requestCapabilityForRoomTimeline = jest.fn().mockResolvedValue(undefined);
@@ -96,6 +103,15 @@ class MockWidgetApi extends EventEmitter {
         send: jest.fn(),
         sendComplete: jest.fn(),
     };
+
+    /**
+     * This mocks the widget's view of what is supported by its environment.
+     * @param clientVersions The versions that the widget believes are supported by the host client's widget driver.
+     */
+    public constructor(clientVersions: ApiVersion[]) {
+        super();
+        this.getClientVersions.mockResolvedValue(clientVersions);
+    }
 }
 
 declare module "../../src/types" {
@@ -117,7 +133,7 @@ describe("RoomWidgetClient", () => {
     let client: MatrixClient;
 
     beforeEach(() => {
-        widgetApi = new MockWidgetApi() as unknown as MockedObject<WidgetApi>;
+        widgetApi = new MockWidgetApi([UnstableApiVersion.MSC2762_UPDATE_STATE]) as unknown as MockedObject<WidgetApi>;
     });
 
     afterEach(() => {
@@ -128,6 +144,7 @@ describe("RoomWidgetClient", () => {
         capabilities: ICapabilities,
         sendContentLoaded: boolean | undefined = undefined,
         userId?: string,
+        useSlidingSync?: boolean,
     ): Promise<void> => {
         const baseUrl = "https://example.org";
         client = createRoomWidgetClient(
@@ -139,7 +156,7 @@ describe("RoomWidgetClient", () => {
         );
         expect(widgetApi.start).toHaveBeenCalled(); // needs to have been called early in order to not miss messages
         widgetApi.emit("ready");
-        await client.startClient();
+        await client.startClient(useSlidingSync ? { slidingSync: new SlidingSync("", new Map(), {}, client, 0) } : {});
     };
 
     describe("events", () => {
@@ -668,10 +685,106 @@ describe("RoomWidgetClient", () => {
                     detail: { data: { state: [event] } },
                 }),
             );
+            // Allow the getClientVersions promise to resolve
+            await flushPromises();
             // It should now have changed the room state
             expect(room!.currentState.getStateEvents("org.example.foo", "bar")?.getEffectiveEvent()).toEqual(event);
         });
 
+        describe("without support for update_state", () => {
+            beforeEach(() => {
+                widgetApi = new MockWidgetApi([]) as unknown as MockedObject<WidgetApi>;
+            });
+
+            it("receives", async () => {
+                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 emittedEvent = new Promise<MatrixEvent>((resolve) => client.once(ClientEvent.Event, resolve));
+                const emittedSync = new Promise<SyncState>((resolve) => client.once(ClientEvent.Sync, resolve));
+                widgetApi.emit(
+                    `action:${WidgetApiToWidgetAction.SendEvent}`,
+                    new CustomEvent(`action:${WidgetApiToWidgetAction.SendEvent}`, { detail: { data: event } }),
+                );
+
+                // The client should've emitted about the received event
+                expect((await emittedEvent).getEffectiveEvent()).toEqual(event);
+                expect(await emittedSync).toEqual(SyncState.Syncing);
+                // It should've also inserted the event into the room object
+                const room = client.getRoom("!1:example.org");
+                expect(room).not.toBeNull();
+                expect(room!.currentState.getStateEvents("org.example.foo", "bar")?.getEffectiveEvent()).toEqual(event);
+            });
+
+            it("does not receive with sliding sync (update_state is needed for sliding sync)", async () => {
+                await makeClient(
+                    { receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] },
+                    undefined,
+                    undefined,
+                    true,
+                );
+                expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
+                expect(widgetApi.requestCapabilityToReceiveState).toHaveBeenCalledWith("org.example.foo", "bar");
+
+                const emittedEvent = new Promise<MatrixEvent>((resolve) => client.once(ClientEvent.Event, resolve));
+                const emittedSync = new Promise<SyncState>((resolve) => client.once(ClientEvent.Sync, resolve));
+                const logSpy = jest.spyOn(logger, "error");
+                widgetApi.emit(
+                    `action:${WidgetApiToWidgetAction.SendEvent}`,
+                    new CustomEvent(`action:${WidgetApiToWidgetAction.SendEvent}`, { detail: { data: event } }),
+                );
+
+                // The client should've emitted about the received event
+                expect((await emittedEvent).getEffectiveEvent()).toEqual(event);
+                expect(await emittedSync).toEqual(SyncState.Syncing);
+
+                // The incompatibility of sliding sync without update_state to get logged.
+                expect(logSpy).toHaveBeenCalledWith(
+                    "slididng sync cannot be used in widget mode if the client widget driver does not support the version: 'org.matrix.msc2762_update_state'",
+                );
+                // It should not have inserted the event into the room object
+                const room = client.getRoom("!1:example.org");
+                expect(room).not.toBeNull();
+                expect(room!.currentState.getStateEvents("org.example.foo", "bar")).toEqual(null);
+            });
+
+            it("backfills", async () => {
+                widgetApi.readStateEvents.mockImplementation(async (eventType, limit, stateKey) =>
+                    eventType === "org.example.foo" && (limit ?? Infinity) > 0 && stateKey === "bar"
+                        ? [event as IRoomEvent]
+                        : [],
+                );
+
+                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);
+            });
+            it("backfills with sliding sync", async () => {
+                widgetApi.readStateEvents.mockImplementation(async (eventType, limit, stateKey) =>
+                    eventType === "org.example.foo" && (limit ?? Infinity) > 0 && stateKey === "bar"
+                        ? [event as IRoomEvent]
+                        : [],
+                );
+                await makeClient(
+                    { receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] },
+                    undefined,
+                    undefined,
+                    true,
+                );
+                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);
+            });
+        });
+
         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
diff --git a/src/embedded.ts b/src/embedded.ts
index 092ff3747c27bd004282ed69b9c9cd8dc4c8fe44..7bc18483c3dba69179c2c16d0a240bfc827bd4b6 100644
--- a/src/embedded.ts
+++ b/src/embedded.ts
@@ -29,6 +29,7 @@ import {
     type IWidgetApiResponse,
     type IWidgetApiResponseData,
     type IUpdateStateToWidgetActionRequest,
+    UnstableApiVersion,
 } from "matrix-widget-api";
 
 import { MatrixEvent, type IEvent, type IContent, EventStatus } from "./models/event.ts";
@@ -259,6 +260,10 @@ export class RoomWidgetClient extends MatrixClient {
         if (sendContentLoaded) widgetApi.sendContentLoaded();
     }
 
+    public async supportUpdateState(): Promise<boolean> {
+        return (await this.widgetApi.getClientVersions()).includes(UnstableApiVersion.MSC2762_UPDATE_STATE);
+    }
+
     public async startClient(opts: IStartClientOpts = {}): Promise<void> {
         this.lifecycle = new AbortController();
 
@@ -283,14 +288,41 @@ export class RoomWidgetClient extends MatrixClient {
 
         await this.widgetApiReady;
 
+        // sync room state:
+        if (await this.supportUpdateState()) {
+            // This will resolve once the client driver has sent us all the allowed room state.
+            await this.roomStateSynced;
+        } else {
+            // 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 events as `stateAfterEventList` 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();
             }, 1000 * opts.clientWellKnownPollPeriod);
             this.fetchClientWellKnown();
         }
-
-        await this.roomStateSynced;
         this.setSyncState(SyncState.Syncing);
         logger.info("Finished initial sync");
 
@@ -589,11 +621,24 @@ export class RoomWidgetClient extends MatrixClient {
             await this.updateTxId(event);
 
             if (this.syncApi instanceof SyncApi) {
-                await this.syncApi.injectRoomEvents(this.room!, undefined, [], [event]);
+                if (await this.supportUpdateState()) {
+                    await this.syncApi.injectRoomEvents(this.room!, undefined, [], [event]);
+                } else {
+                    // Passing undefined for `stateAfterEventList` 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]);
+                if (await this.supportUpdateState()) {
+                    await this.syncApi!.injectRoomEvents(this.room!, [], [event]);
+                } else {
+                    logger.error(
+                        "slididng sync cannot be used in widget mode if the client widget driver does not support the version: 'org.matrix.msc2762_update_state'",
+                    );
+                }
             }
+
             this.emit(ClientEvent.Event, event);
             this.setSyncState(SyncState.Syncing);
             logger.info(`Received event ${event.getId()} ${event.getType()}`);
@@ -623,7 +668,11 @@ export class RoomWidgetClient extends MatrixClient {
 
     private onStateUpdate = async (ev: CustomEvent<IUpdateStateToWidgetActionRequest>): Promise<void> => {
         ev.preventDefault();
-
+        if (!(await this.supportUpdateState())) {
+            logger.warn(
+                "received update_state widget action but the widget driver did not claim to support 'org.matrix.msc2762_update_state'",
+            );
+        }
         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