diff --git a/.changeset/brave-pears-add.md b/.changeset/brave-pears-add.md
new file mode 100644
index 00000000000..74870af7af9
--- /dev/null
+++ b/.changeset/brave-pears-add.md
@@ -0,0 +1,7 @@
+---
+'@clerk/clerk-js': minor
+'@clerk/types': minor
+'@clerk/clerk-react': minor
+---
+
+Navigate to tasks on after sign-in/sign-up
diff --git a/integration/.keys.json.sample b/integration/.keys.json.sample
index caa70922c39..1cb3b47dc50 100644
--- a/integration/.keys.json.sample
+++ b/integration/.keys.json.sample
@@ -46,5 +46,9 @@
   "with-waitlist-mode": {
     "pk": "",
     "sk": ""
+  },
+ "with-session-tasks": {
+    "pk": "",
+    "sk": ""
   }
 }
diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts
index 4fbf4d2bb22..8254244547f 100644
--- a/integration/presets/envs.ts
+++ b/integration/presets/envs.ts
@@ -137,6 +137,13 @@ const withSignInOrUpwithRestrictedModeFlow = withEmailCodes
   .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-restricted-mode').pk)
   .setEnvVariable('public', 'CLERK_SIGN_UP_URL', undefined);
 
+const withSessionTasks = base
+  .clone()
+  .setId('withSessionTasks')
+  .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-session-tasks').sk)
+  .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-session-tasks').pk)
+  .setEnvVariable('private', 'CLERK_ENCRYPTION_KEY', constants.E2E_CLERK_ENCRYPTION_KEY || 'a-key');
+
 export const envs = {
   base,
   withKeyless,
@@ -157,4 +164,5 @@ export const envs = {
   withSignInOrUpFlow,
   withSignInOrUpEmailLinksFlow,
   withSignInOrUpwithRestrictedModeFlow,
+  withSessionTasks,
 } as const;
diff --git a/integration/presets/longRunningApps.ts b/integration/presets/longRunningApps.ts
index d5573f015e0..f595591a3c3 100644
--- a/integration/presets/longRunningApps.ts
+++ b/integration/presets/longRunningApps.ts
@@ -37,6 +37,11 @@ export const createLongRunningApps = () => {
       config: next.appRouter,
       env: envs.withSignInOrUpEmailLinksFlow,
     },
+    {
+      id: 'next.appRouter.withSessionTasks',
+      config: next.appRouter,
+      env: envs.withSessionTasks,
+    },
     { id: 'quickstart.next.appRouter', config: next.appRouterQuickstart, env: envs.withEmailCodesQuickstart },
     { id: 'elements.next.appRouter', config: elements.nextAppRouter, env: envs.withEmailCodes },
     { id: 'astro.node.withCustomRoles', config: astro.node, env: envs.withCustomRoles },
diff --git a/integration/templates/next-app-router/src/app/layout.tsx b/integration/templates/next-app-router/src/app/layout.tsx
index 2e56184f39d..e598334db29 100644
--- a/integration/templates/next-app-router/src/app/layout.tsx
+++ b/integration/templates/next-app-router/src/app/layout.tsx
@@ -24,6 +24,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
         persistClient: process.env.NEXT_PUBLIC_EXPERIMENTAL_PERSIST_CLIENT
           ? process.env.NEXT_PUBLIC_EXPERIMENTAL_PERSIST_CLIENT === 'true'
           : undefined,
+        // `experimental.withSessionTasks` will be removed soon in favor of checking via environment response
+        withSessionTasks: true,
       }}
     >
       <html lang='en'>
diff --git a/integration/tests/session-tasks-sign-in.test.ts b/integration/tests/session-tasks-sign-in.test.ts
new file mode 100644
index 00000000000..eebc35002c1
--- /dev/null
+++ b/integration/tests/session-tasks-sign-in.test.ts
@@ -0,0 +1,38 @@
+import { expect, test } from '@playwright/test';
+
+import { appConfigs } from '../presets';
+import type { FakeUser } from '../testUtils';
+import { createTestUtils, testAgainstRunningApps } from '../testUtils';
+
+testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasks] })(
+  'session tasks after sign-in flow @nextjs',
+  ({ app }) => {
+    test.describe.configure({ mode: 'serial' });
+
+    let fakeUser: FakeUser;
+
+    test.beforeAll(async () => {
+      const u = createTestUtils({ app });
+      fakeUser = u.services.users.createFakeUser();
+      await u.services.users.createBapiUser(fakeUser);
+    });
+
+    test.afterAll(async () => {
+      await fakeUser.deleteIfExists();
+      await app.teardown();
+    });
+
+    test('navigate to task on after sign-in', async ({ page, context }) => {
+      const u = createTestUtils({ app, page, context });
+      await u.po.signIn.goTo();
+      await u.po.signIn.setIdentifier(fakeUser.email);
+      await u.po.signIn.continue();
+      await u.po.signIn.setPassword(fakeUser.password);
+      await u.po.signIn.continue();
+      await u.po.expect.toBeSignedIn();
+
+      await expect(u.page.getByRole('button', { name: /create organization/i })).toBeVisible();
+      expect(page.url()).toContain('add-organization');
+    });
+  },
+);
diff --git a/integration/tests/session-tasks-sign-up.test.ts b/integration/tests/session-tasks-sign-up.test.ts
new file mode 100644
index 00000000000..ad5003eb2f3
--- /dev/null
+++ b/integration/tests/session-tasks-sign-up.test.ts
@@ -0,0 +1,34 @@
+import { expect, test } from '@playwright/test';
+
+import { appConfigs } from '../presets';
+import { createTestUtils, testAgainstRunningApps } from '../testUtils';
+
+testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasks] })(
+  'session tasks after sign-up flow @nextjs',
+  ({ app }) => {
+    test.describe.configure({ mode: 'serial' });
+
+    test.afterAll(async () => {
+      await app.teardown();
+    });
+
+    test('navigate to task on after sign-up', async ({ page, context }) => {
+      const u = createTestUtils({ app, page, context });
+      const fakeUser = u.services.users.createFakeUser({
+        fictionalEmail: true,
+        withPhoneNumber: true,
+        withUsername: true,
+      });
+      await u.po.signUp.goTo();
+      await u.po.signUp.signUpWithEmailAndPassword({
+        email: fakeUser.email,
+        password: fakeUser.password,
+      });
+
+      await expect(u.page.getByRole('button', { name: /create organization/i })).toBeVisible();
+      expect(page.url()).toContain('add-organization');
+
+      await fakeUser.deleteIfExists();
+    });
+  },
+);
diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json
index d9133f5333a..d5015e80235 100644
--- a/packages/clerk-js/bundlewatch.config.json
+++ b/packages/clerk-js/bundlewatch.config.json
@@ -1,7 +1,7 @@
 {
   "files": [
-    { "path": "./dist/clerk.js", "maxSize": "570kB" },
-    { "path": "./dist/clerk.browser.js", "maxSize": "76kB" },
+    { "path": "./dist/clerk.js", "maxSize": "572kB" },
+    { "path": "./dist/clerk.browser.js", "maxSize": "78kB" },
     { "path": "./dist/clerk.headless.js", "maxSize": "50KB" },
     { "path": "./dist/ui-common*.js", "maxSize": "92KB" },
     { "path": "./dist/vendors*.js", "maxSize": "26.5KB" },
@@ -12,7 +12,7 @@
     { "path": "./dist/organizationswitcher*.js", "maxSize": "5KB" },
     { "path": "./dist/organizationlist*.js", "maxSize": "5.5KB" },
     { "path": "./dist/signin*.js", "maxSize": "12.4KB" },
-    { "path": "./dist/signup*.js", "maxSize": "6.4KB" },
+    { "path": "./dist/signup*.js", "maxSize": "6.5KB" },
     { "path": "./dist/userbutton*.js", "maxSize": "5KB" },
     { "path": "./dist/userprofile*.js", "maxSize": "15KB" },
     { "path": "./dist/userverification*.js", "maxSize": "5KB" },
diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts
index 48f0a85a291..ea573685bd0 100644
--- a/packages/clerk-js/src/core/__tests__/clerk.test.ts
+++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts
@@ -155,163 +155,302 @@ describe('Clerk singleton', () => {
   });
 
   describe('.setActive', () => {
-    describe.each(['active', 'pending'] satisfies Array<SignedInSessionResource['status']>)(
-      'when session has %s status',
-      status => {
-        const mockSession = {
-          id: '1',
-          remove: jest.fn(),
-          status,
-          user: {},
-          touch: jest.fn(() => Promise.resolve()),
-          getToken: jest.fn(),
-          lastActiveToken: { getRawString: () => 'mocked-token' },
-        };
-        let eventBusSpy;
+    describe('with `active` session status', () => {
+      const mockSession = {
+        id: '1',
+        remove: jest.fn(),
+        status: 'active',
+        user: {},
+        touch: jest.fn(() => Promise.resolve()),
+        getToken: jest.fn(),
+        lastActiveToken: { getRawString: () => 'mocked-token' },
+      };
+      let eventBusSpy;
 
-        beforeEach(() => {
-          eventBusSpy = jest.spyOn(eventBus, 'dispatch');
-        });
+      beforeEach(() => {
+        eventBusSpy = jest.spyOn(eventBus, 'dispatch');
+      });
 
-        afterEach(() => {
-          mockSession.remove.mockReset();
-          mockSession.touch.mockReset();
+      afterEach(() => {
+        mockSession.remove.mockReset();
+        mockSession.touch.mockReset();
 
-          eventBusSpy?.mockRestore();
-          // cleanup global window pollution
-          (window as any).__unstable__onBeforeSetActive = null;
-          (window as any).__unstable__onAfterSetActive = null;
+        eventBusSpy?.mockRestore();
+        // cleanup global window pollution
+        (window as any).__unstable__onBeforeSetActive = null;
+        (window as any).__unstable__onAfterSetActive = null;
+      });
+
+      it('does not call session touch on signOut', async () => {
+        mockSession.touch.mockReturnValueOnce(Promise.resolve());
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
+        await sut.setActive({ session: null });
+        await waitFor(() => {
+          expect(mockSession.touch).not.toHaveBeenCalled();
+          expect(eventBusSpy).toHaveBeenCalledWith('token:update', { token: null });
         });
+      });
 
-        it('does not call session touch on signOut', async () => {
-          mockSession.touch.mockReturnValueOnce(Promise.resolve());
-          mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+      it('calls session.touch by default', async () => {
+        mockSession.touch.mockReturnValue(Promise.resolve());
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
 
-          const sut = new Clerk(productionPublishableKey);
-          await sut.load();
-          await sut.setActive({ session: null });
-          await waitFor(() => {
-            expect(mockSession.touch).not.toHaveBeenCalled();
-            expect(eventBusSpy).toHaveBeenCalledWith('token:update', { token: null });
-          });
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
+        await sut.setActive({ session: mockSession as any as ActiveSessionResource });
+        expect(mockSession.touch).toHaveBeenCalled();
+      });
+
+      it('does not call session.touch if Clerk was initialised with touchSession set to false', async () => {
+        mockSession.touch.mockReturnValueOnce(Promise.resolve());
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+        mockSession.getToken.mockResolvedValue('mocked-token');
+
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load({ touchSession: false });
+        await sut.setActive({ session: mockSession as any as ActiveSessionResource });
+        await waitFor(() => {
+          expect(mockSession.touch).not.toHaveBeenCalled();
+          expect(mockSession.getToken).toHaveBeenCalled();
         });
+      });
 
-        it('calls session.touch by default', async () => {
-          mockSession.touch.mockReturnValue(Promise.resolve());
-          mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+      it('calls __unstable__onBeforeSetActive before session.touch', async () => {
+        mockSession.touch.mockReturnValueOnce(Promise.resolve());
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
 
-          const sut = new Clerk(productionPublishableKey);
-          await sut.load();
-          await sut.setActive({ session: mockSession as any as ActiveSessionResource });
+        (window as any).__unstable__onBeforeSetActive = () => {
+          expect(mockSession.touch).not.toHaveBeenCalled();
+        };
+
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
+        await sut.setActive({ session: mockSession as any as ActiveSessionResource });
+        expect(mockSession.touch).toHaveBeenCalled();
+      });
+
+      it('sets __session and __client_uat cookie before calling __unstable__onBeforeSetActive', async () => {
+        mockSession.touch.mockReturnValueOnce(Promise.resolve());
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+
+        (window as any).__unstable__onBeforeSetActive = () => {
+          expect(eventBusSpy).toHaveBeenCalledWith('token:update', { token: mockSession.lastActiveToken });
+        };
+
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
+        await sut.setActive({ session: mockSession as any as ActiveSessionResource });
+      });
+
+      it('calls __unstable__onAfterSetActive after beforeEmit and session.touch', async () => {
+        const beforeEmitMock = jest.fn();
+        mockSession.touch.mockReturnValueOnce(Promise.resolve());
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+
+        (window as any).__unstable__onAfterSetActive = () => {
           expect(mockSession.touch).toHaveBeenCalled();
-        });
+          expect(beforeEmitMock).toHaveBeenCalled();
+        };
 
-        it('does not call session.touch if Clerk was initialised with touchSession set to false', async () => {
-          mockSession.touch.mockReturnValueOnce(Promise.resolve());
-          mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
-          mockSession.getToken.mockResolvedValue('mocked-token');
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
+        await sut.setActive({ session: mockSession as any as ActiveSessionResource, beforeEmit: beforeEmitMock });
+      });
 
-          const sut = new Clerk(productionPublishableKey);
-          await sut.load({ touchSession: false });
-          await sut.setActive({ session: mockSession as any as ActiveSessionResource });
-          await waitFor(() => {
-            expect(mockSession.touch).not.toHaveBeenCalled();
-            expect(mockSession.getToken).toHaveBeenCalled();
-          });
+      // TODO: @dimkl include set transitive state
+      it('calls session.touch -> set cookie -> before emit with touched session on session switch', async () => {
+        const mockSession2 = {
+          id: '2',
+          remove: jest.fn(),
+          status: 'active',
+          user: {},
+          touch: jest.fn(),
+          getToken: jest.fn(),
+        };
+        mockClientFetch.mockReturnValue(
+          Promise.resolve({
+            signedInSessions: [mockSession, mockSession2],
+          }),
+        );
+
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
+
+        const executionOrder: string[] = [];
+        mockSession2.touch.mockImplementationOnce(() => {
+          sut.session = mockSession2 as any;
+          executionOrder.push('session.touch');
+          return Promise.resolve();
+        });
+        mockSession2.getToken.mockImplementation(() => {
+          executionOrder.push('set cookie');
+          return 'mocked-token-2';
+        });
+        const beforeEmitMock = jest.fn().mockImplementationOnce(() => {
+          executionOrder.push('before emit');
+          return Promise.resolve();
         });
 
-        it('calls __unstable__onBeforeSetActive before session.touch', async () => {
-          mockSession.touch.mockReturnValueOnce(Promise.resolve());
-          mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+        await sut.setActive({ session: mockSession2 as any as ActiveSessionResource, beforeEmit: beforeEmitMock });
 
-          (window as any).__unstable__onBeforeSetActive = () => {
-            expect(mockSession.touch).not.toHaveBeenCalled();
-          };
+        await waitFor(() => {
+          expect(executionOrder).toEqual(['session.touch', 'set cookie', 'before emit']);
+          expect(mockSession2.touch).toHaveBeenCalled();
+          expect(mockSession2.getToken).toHaveBeenCalled();
+          expect(beforeEmitMock).toHaveBeenCalledWith(mockSession2);
+          expect(sut.session).toMatchObject(mockSession2);
+        });
+      });
 
-          const sut = new Clerk(productionPublishableKey);
-          await sut.load();
-          await sut.setActive({ session: mockSession as any as ActiveSessionResource });
-          expect(mockSession.touch).toHaveBeenCalled();
+      // TODO: @dimkl include set transitive state
+      it('calls with lastActiveOrganizationId session.touch -> set cookie -> before emit -> set accessors with touched session on organization switch', async () => {
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
+
+        const executionOrder: string[] = [];
+        mockSession.touch.mockImplementationOnce(() => {
+          sut.session = mockSession as any;
+          executionOrder.push('session.touch');
+          return Promise.resolve();
+        });
+        mockSession.getToken.mockImplementation(() => {
+          executionOrder.push('set cookie');
+          return 'mocked-token';
         });
 
-        it('sets __session and __client_uat cookie before calling __unstable__onBeforeSetActive', async () => {
-          mockSession.touch.mockReturnValueOnce(Promise.resolve());
-          mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+        const beforeEmitMock = jest.fn().mockImplementationOnce(() => {
+          executionOrder.push('before emit');
+          return Promise.resolve();
+        });
 
-          (window as any).__unstable__onBeforeSetActive = () => {
-            expect(eventBusSpy).toHaveBeenCalledWith('token:update', { token: mockSession.lastActiveToken });
-          };
+        await sut.setActive({ organization: { id: 'org_id' } as Organization, beforeEmit: beforeEmitMock });
 
-          const sut = new Clerk(productionPublishableKey);
-          await sut.load();
-          await sut.setActive({ session: mockSession as any as ActiveSessionResource });
+        await waitFor(() => {
+          expect(executionOrder).toEqual(['session.touch', 'set cookie', 'before emit']);
+          expect(mockSession.touch).toHaveBeenCalled();
+          expect(mockSession.getToken).toHaveBeenCalled();
+          expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org_id');
+          expect(beforeEmitMock).toHaveBeenCalledWith(mockSession);
+          expect(sut.session).toMatchObject(mockSession);
         });
+      });
 
-        it('calls __unstable__onAfterSetActive after beforeEmit and session.touch', async () => {
-          const beforeEmitMock = jest.fn();
-          mockSession.touch.mockReturnValueOnce(Promise.resolve());
-          mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+      it('sets active organization by slug', async () => {
+        const mockSession2 = {
+          id: '1',
+          status,
+          user: {
+            organizationMemberships: [
+              {
+                id: 'orgmem_id',
+                organization: {
+                  id: 'org_id',
+                  slug: 'some-org-slug',
+                },
+              },
+            ],
+          },
+          touch: jest.fn(),
+          getToken: jest.fn(),
+        };
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession2] }));
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
 
-          (window as any).__unstable__onAfterSetActive = () => {
-            expect(mockSession.touch).toHaveBeenCalled();
-            expect(beforeEmitMock).toHaveBeenCalled();
-          };
+        mockSession2.touch.mockImplementationOnce(() => {
+          sut.session = mockSession2 as any;
+          return Promise.resolve();
+        });
+        mockSession2.getToken.mockImplementation(() => 'mocked-token');
 
-          const sut = new Clerk(productionPublishableKey);
-          await sut.load();
-          await sut.setActive({ session: mockSession as any as ActiveSessionResource, beforeEmit: beforeEmitMock });
+        await sut.setActive({ organization: 'some-org-slug' });
+
+        await waitFor(() => {
+          expect(mockSession2.touch).toHaveBeenCalled();
+          expect(mockSession2.getToken).toHaveBeenCalled();
+          expect((mockSession2 as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org_id');
+          expect(sut.session).toMatchObject(mockSession2);
         });
+      });
 
-        // TODO: @dimkl include set transitive state
-        it('calls session.touch -> set cookie -> before emit with touched session on session switch', async () => {
-          const mockSession2 = {
-            id: '2',
-            remove: jest.fn(),
-            status: 'active',
-            user: {},
-            touch: jest.fn(),
-            getToken: jest.fn(),
-          };
-          mockClientFetch.mockReturnValue(
-            Promise.resolve({
-              signedInSessions: [mockSession, mockSession2],
-            }),
-          );
+      it('redirects the user to the /v1/client/touch endpoint if the cookie_expires_at is less than 8 days away', async () => {
+        mockSession.touch.mockReturnValue(Promise.resolve());
+        mockClientFetch.mockReturnValue(
+          Promise.resolve({
+            signedInSessions: [mockSession],
+            cookieExpiresAt: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now
+            isEligibleForTouch: () => true,
+            buildTouchUrl: () =>
+              `https://clerk.example.com/v1/client/touch?redirect_url=${mockWindowLocation.href}/redirect-url-path`,
+          }),
+        );
 
-          const sut = new Clerk(productionPublishableKey);
-          await sut.load();
+        const sut = new Clerk(productionPublishableKey);
+        sut.navigate = jest.fn();
+        await sut.load();
+        await sut.setActive({
+          session: mockSession as any as ActiveSessionResource,
+          redirectUrl: '/redirect-url-path',
+        });
+        const redirectUrl = new URL((sut.navigate as jest.Mock).mock.calls[0]);
+        expect(redirectUrl.pathname).toEqual('/v1/client/touch');
+        expect(redirectUrl.searchParams.get('redirect_url')).toEqual(`${mockWindowLocation.href}/redirect-url-path`);
+      });
 
-          const executionOrder: string[] = [];
-          mockSession2.touch.mockImplementationOnce(() => {
-            sut.session = mockSession2 as any;
-            executionOrder.push('session.touch');
-            return Promise.resolve();
-          });
-          mockSession2.getToken.mockImplementation(() => {
-            executionOrder.push('set cookie');
-            return 'mocked-token-2';
-          });
-          const beforeEmitMock = jest.fn().mockImplementationOnce(() => {
-            executionOrder.push('before emit');
-            return Promise.resolve();
-          });
+      it('does not redirect the user to the /v1/client/touch endpoint if the cookie_expires_at is more than 8 days away', async () => {
+        mockSession.touch.mockReturnValue(Promise.resolve());
+        mockClientFetch.mockReturnValue(
+          Promise.resolve({
+            signedInSessions: [mockSession],
+            cookieExpiresAt: new Date(Date.now() + 10 * 24 * 60 * 60 * 1000), // 10 days from now
+            isEligibleForTouch: () => false,
+            buildTouchUrl: () =>
+              `https://clerk.example.com/v1/client/touch?redirect_url=${mockWindowLocation.href}/redirect-url-path`,
+          }),
+        );
 
-          await sut.setActive({ session: mockSession2 as any as ActiveSessionResource, beforeEmit: beforeEmitMock });
+        const sut = new Clerk(productionPublishableKey);
+        sut.navigate = jest.fn();
+        await sut.load();
+        await sut.setActive({
+          session: mockSession as any as ActiveSessionResource,
+          redirectUrl: '/redirect-url-path',
+        });
+        expect(sut.navigate).toHaveBeenCalledWith('/redirect-url-path');
+      });
 
-          await waitFor(() => {
-            expect(executionOrder).toEqual(['session.touch', 'set cookie', 'before emit']);
-            expect(mockSession2.touch).toHaveBeenCalled();
-            expect(mockSession2.getToken).toHaveBeenCalled();
-            expect(beforeEmitMock).toHaveBeenCalledWith(mockSession2);
-            expect(sut.session).toMatchObject(mockSession2);
-          });
+      it('does not redirect the user to the /v1/client/touch endpoint if the cookie_expires_at is not set', async () => {
+        mockSession.touch.mockReturnValue(Promise.resolve());
+        mockClientFetch.mockReturnValue(
+          Promise.resolve({
+            signedInSessions: [mockSession],
+            cookieExpiresAt: null,
+            isEligibleForTouch: () => false,
+            buildTouchUrl: () =>
+              `https://clerk.example.com/v1/client/touch?redirect_url=${mockWindowLocation.href}/redirect-url-path`,
+          }),
+        );
+
+        const sut = new Clerk(productionPublishableKey);
+        sut.navigate = jest.fn();
+        await sut.load();
+        await sut.setActive({
+          session: mockSession as any as ActiveSessionResource,
+          redirectUrl: '/redirect-url-path',
         });
+        expect(sut.navigate).toHaveBeenCalledWith('/redirect-url-path');
+      });
 
-        // TODO: @dimkl include set transitive state
-        it('calls with lastActiveOrganizationId session.touch -> set cookie -> before emit -> set accessors with touched session on organization switch', async () => {
+      mockNativeRuntime(() => {
+        it('calls session.touch in a non-standard browser', async () => {
           mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+
           const sut = new Clerk(productionPublishableKey);
-          await sut.load();
+          await sut.load({ standardBrowser: false });
 
           const executionOrder: string[] = [];
           mockSession.touch.mockImplementationOnce(() => {
@@ -319,11 +458,6 @@ describe('Clerk singleton', () => {
             executionOrder.push('session.touch');
             return Promise.resolve();
           });
-          mockSession.getToken.mockImplementation(() => {
-            executionOrder.push('set cookie');
-            return 'mocked-token';
-          });
-
           const beforeEmitMock = jest.fn().mockImplementationOnce(() => {
             executionOrder.push('before emit');
             return Promise.resolve();
@@ -331,152 +465,98 @@ describe('Clerk singleton', () => {
 
           await sut.setActive({ organization: { id: 'org_id' } as Organization, beforeEmit: beforeEmitMock });
 
-          await waitFor(() => {
-            expect(executionOrder).toEqual(['session.touch', 'set cookie', 'before emit']);
-            expect(mockSession.touch).toHaveBeenCalled();
-            expect(mockSession.getToken).toHaveBeenCalled();
-            expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org_id');
-            expect(beforeEmitMock).toHaveBeenCalledWith(mockSession);
-            expect(sut.session).toMatchObject(mockSession);
-          });
+          expect(executionOrder).toEqual(['session.touch', 'before emit']);
+          expect(mockSession.touch).toHaveBeenCalled();
+          expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org_id');
+          expect(mockSession.getToken).toHaveBeenCalled();
+          expect(beforeEmitMock).toHaveBeenCalledWith(mockSession);
+          expect(sut.session).toMatchObject(mockSession);
         });
+      });
+    });
 
-        it('sets active organization by slug', async () => {
-          const mockSession2 = {
-            id: '1',
-            status,
-            user: {
-              organizationMemberships: [
-                {
-                  id: 'orgmem_id',
-                  organization: {
-                    id: 'org_id',
-                    slug: 'some-org-slug',
-                  },
-                },
-              ],
-            },
-            touch: jest.fn(),
-            getToken: jest.fn(),
-          };
-          mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession2] }));
-          const sut = new Clerk(productionPublishableKey);
-          await sut.load();
+    describe('with `pending` session status', () => {
+      const mockSession = {
+        id: '1',
+        remove: jest.fn(),
+        status: 'pending',
+        user: {},
+        touch: jest.fn(() => Promise.resolve()),
+        getToken: jest.fn(),
+        lastActiveToken: { getRawString: () => 'mocked-token' },
+        tasks: [{ key: 'org' }],
+        currentTask: { key: 'org', __internal_getUrl: () => 'https://foocorp.com/add-organization' },
+      };
+      let eventBusSpy;
 
-          mockSession2.touch.mockImplementationOnce(() => {
-            sut.session = mockSession2 as any;
-            return Promise.resolve();
-          });
-          mockSession2.getToken.mockImplementation(() => 'mocked-token');
+      beforeEach(() => {
+        eventBusSpy = jest.spyOn(eventBus, 'dispatch');
+      });
 
-          await sut.setActive({ organization: 'some-org-slug' });
+      afterEach(() => {
+        mockSession.remove.mockReset();
+        mockSession.touch.mockReset();
 
-          await waitFor(() => {
-            expect(mockSession2.touch).toHaveBeenCalled();
-            expect(mockSession2.getToken).toHaveBeenCalled();
-            expect((mockSession2 as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org_id');
-            expect(sut.session).toMatchObject(mockSession2);
-          });
-        });
+        eventBusSpy?.mockRestore();
+        // cleanup global window pollution
+        (window as any).__unstable__onBeforeSetActive = null;
+        (window as any).__unstable__onAfterSetActive = null;
+      });
 
-        it('redirects the user to the /v1/client/touch endpoint if the cookie_expires_at is less than 8 days away', async () => {
-          mockSession.touch.mockReturnValue(Promise.resolve());
-          mockClientFetch.mockReturnValue(
-            Promise.resolve({
-              signedInSessions: [mockSession],
-              cookieExpiresAt: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now
-              isEligibleForTouch: () => true,
-              buildTouchUrl: () =>
-                `https://clerk.example.com/v1/client/touch?redirect_url=${mockWindowLocation.href}/redirect-url-path`,
-            }),
-          );
+      it('calls session.touch by default', async () => {
+        mockSession.touch.mockReturnValue(Promise.resolve());
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
 
-          const sut = new Clerk(productionPublishableKey);
-          sut.navigate = jest.fn();
-          await sut.load();
-          await sut.setActive({
-            session: mockSession as any as ActiveSessionResource,
-            redirectUrl: '/redirect-url-path',
-          });
-          const redirectUrl = new URL((sut.navigate as jest.Mock).mock.calls[0]);
-          expect(redirectUrl.pathname).toEqual('/v1/client/touch');
-          expect(redirectUrl.searchParams.get('redirect_url')).toEqual(`${mockWindowLocation.href}/redirect-url-path`);
-        });
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
+        await sut.setActive({ session: mockSession as any as ActiveSessionResource });
+        expect(mockSession.touch).toHaveBeenCalled();
+      });
 
-        it('does not redirect the user to the /v1/client/touch endpoint if the cookie_expires_at is more than 8 days away', async () => {
-          mockSession.touch.mockReturnValue(Promise.resolve());
-          mockClientFetch.mockReturnValue(
-            Promise.resolve({
-              signedInSessions: [mockSession],
-              cookieExpiresAt: new Date(Date.now() + 10 * 24 * 60 * 60 * 1000), // 10 days from now
-              isEligibleForTouch: () => false,
-              buildTouchUrl: () =>
-                `https://clerk.example.com/v1/client/touch?redirect_url=${mockWindowLocation.href}/redirect-url-path`,
-            }),
-          );
+      it('does not call session.touch if Clerk was initialised with touchSession set to false', async () => {
+        mockSession.touch.mockReturnValueOnce(Promise.resolve());
+        mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
+        mockSession.getToken.mockResolvedValue('mocked-token');
 
-          const sut = new Clerk(productionPublishableKey);
-          sut.navigate = jest.fn();
-          await sut.load();
-          await sut.setActive({
-            session: mockSession as any as ActiveSessionResource,
-            redirectUrl: '/redirect-url-path',
-          });
-          expect(sut.navigate).toHaveBeenCalledWith('/redirect-url-path');
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load({ touchSession: false });
+        await sut.setActive({ session: mockSession as any as ActiveSessionResource });
+        await waitFor(() => {
+          expect(mockSession.touch).not.toHaveBeenCalled();
+          expect(mockSession.getToken).toHaveBeenCalled();
         });
+      });
 
-        it('does not redirect the user to the /v1/client/touch endpoint if the cookie_expires_at is not set', async () => {
-          mockSession.touch.mockReturnValue(Promise.resolve());
-          mockClientFetch.mockReturnValue(
-            Promise.resolve({
-              signedInSessions: [mockSession],
-              cookieExpiresAt: null,
-              isEligibleForTouch: () => false,
-              buildTouchUrl: () =>
-                `https://clerk.example.com/v1/client/touch?redirect_url=${mockWindowLocation.href}/redirect-url-path`,
-            }),
-          );
+      it('navigates to task and set accessors with touched session', async () => {
+        mockClientFetch.mockReturnValue(Promise.resolve({ sessions: [mockSession], signedInSessions: [mockSession] }));
+        const sut = new Clerk(productionPublishableKey);
+        await sut.load();
 
-          const sut = new Clerk(productionPublishableKey);
-          sut.navigate = jest.fn();
-          await sut.load();
-          await sut.setActive({
-            session: mockSession as any as ActiveSessionResource,
-            redirectUrl: '/redirect-url-path',
-          });
-          expect(sut.navigate).toHaveBeenCalledWith('/redirect-url-path');
+        const executionOrder: string[] = [];
+        mockSession.touch.mockImplementationOnce(() => {
+          sut.session = mockSession as any;
+          executionOrder.push('session.touch');
+          return Promise.resolve();
+        });
+        mockSession.getToken.mockImplementation(() => {
+          executionOrder.push('set cookie');
+          return 'mocked-token';
+        });
+        sut.navigate = jest.fn().mockImplementationOnce(() => {
+          executionOrder.push('navigate');
+          return Promise.resolve();
         });
 
-        mockNativeRuntime(() => {
-          it('calls session.touch in a non-standard browser', async () => {
-            mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] }));
-
-            const sut = new Clerk(productionPublishableKey);
-            await sut.load({ standardBrowser: false });
-
-            const executionOrder: string[] = [];
-            mockSession.touch.mockImplementationOnce(() => {
-              sut.session = mockSession as any;
-              executionOrder.push('session.touch');
-              return Promise.resolve();
-            });
-            const beforeEmitMock = jest.fn().mockImplementationOnce(() => {
-              executionOrder.push('before emit');
-              return Promise.resolve();
-            });
-
-            await sut.setActive({ organization: { id: 'org_id' } as Organization, beforeEmit: beforeEmitMock });
-
-            expect(executionOrder).toEqual(['session.touch', 'before emit']);
-            expect(mockSession.touch).toHaveBeenCalled();
-            expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org_id');
-            expect(mockSession.getToken).toHaveBeenCalled();
-            expect(beforeEmitMock).toHaveBeenCalledWith(mockSession);
-            expect(sut.session).toMatchObject(mockSession);
-          });
+        await sut.setActive({ session: mockSession.id });
+
+        await waitFor(() => {
+          expect(executionOrder).toEqual(['session.touch', 'set cookie', 'navigate']);
+          expect(mockSession.touch).toHaveBeenCalled();
+          expect(mockSession.getToken).toHaveBeenCalled();
+          expect(sut.session).toMatchObject(mockSession);
         });
-      },
-    );
+      });
+    });
   });
 
   describe('.load()', () => {
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index 454792854d8..32f0fb7ece7 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -131,6 +131,7 @@ import {
   Organization,
   Waitlist,
 } from './resources/internal';
+import { navigateToTask } from './sessionTasks';
 import { warnings } from './warnings';
 
 type SetActiveHook = (intent?: 'sign-out') => void | Promise<void>;
@@ -199,6 +200,15 @@ export class Clerk implements ClerkInterface {
   #options: ClerkOptions = {};
   #pageLifecycle: ReturnType<typeof createPageLifecycle> | null = null;
   #touchThrottledUntil = 0;
+  #componentNavigationContext: {
+    navigate: (
+      to: string,
+      options?: {
+        searchParams?: URLSearchParams;
+      },
+    ) => Promise<unknown>;
+    basePath: string;
+  } | null = null;
 
   public __internal_getCachedResources:
     | (() => Promise<{ client: ClientJSONSnapshot | null; environment: EnvironmentJSONSnapshot | null }>)
@@ -954,6 +964,11 @@ export class Clerk implements ClerkInterface {
       session = (this.client.sessions.find(x => x.id === session) as SignedInSessionResource) || null;
     }
 
+    if (session?.status === 'pending') {
+      await this.#handlePendingSession(session);
+      return;
+    }
+
     let newSession = session === undefined ? this.session : session;
 
     // At this point, the `session` variable should contain either an `SignedInSessionResource`
@@ -1043,6 +1058,38 @@ export class Clerk implements ClerkInterface {
     await onAfterSetActive();
   };
 
+  #handlePendingSession = async (session: SignedInSessionResource) => {
+    if (!this.environment) {
+      return;
+    }
+
+    // Handles multi-session scenario when switching from `active`
+    // to `pending`
+    if (inActiveBrowserTab() || !this.#options.standardBrowser) {
+      await this.#touchCurrentSession(session);
+      session = this.#getSessionFromClient(session.id) ?? session;
+    }
+
+    // Syncs __session and __client_uat, in case the `pending` session
+    // has expired, it needs to trigger a sign-out
+    const token = await session.getToken();
+    if (!token) {
+      eventBus.dispatch(events.TokenUpdate, { token: null });
+    }
+
+    if (session.currentTask) {
+      await navigateToTask(session.currentTask, {
+        globalNavigate: this.navigate,
+        componentNavigationContext: this.#componentNavigationContext,
+        options: this.#options,
+        environment: this.environment,
+      });
+    }
+
+    this.#setAccessors(session);
+    this.#emit();
+  };
+
   public addListener = (listener: ListenerCallback): UnsubscribeCallback => {
     listener = memoizeListenerCallback(listener);
     this.#listeners.push(listener);
@@ -1070,6 +1117,20 @@ export class Clerk implements ClerkInterface {
     return unsubscribe;
   };
 
+  public __internal_setComponentNavigationContext = (context: {
+    navigate: (
+      to: string,
+      options?: {
+        searchParams?: URLSearchParams;
+      },
+    ) => Promise<unknown>;
+    basePath: string;
+  }) => {
+    this.#componentNavigationContext = context;
+
+    return () => (this.#componentNavigationContext = null);
+  };
+
   public navigate = async (to: string | undefined, options?: NavigateOptions): Promise<unknown> => {
     if (!to || !inBrowser()) {
       return;
diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts
index d3972d75327..1c9aa745b69 100644
--- a/packages/clerk-js/src/core/resources/Session.ts
+++ b/packages/clerk-js/src/core/resources/Session.ts
@@ -363,4 +363,9 @@ export class Session extends BaseResource implements SessionResource {
       return token.getRawString() || null;
     });
   }
+
+  get currentTask() {
+    const [task] = this.tasks ?? [];
+    return task;
+  }
 }
diff --git a/packages/clerk-js/src/core/sessionTasks.ts b/packages/clerk-js/src/core/sessionTasks.ts
new file mode 100644
index 00000000000..29d7d9e0481
--- /dev/null
+++ b/packages/clerk-js/src/core/sessionTasks.ts
@@ -0,0 +1,53 @@
+import type { ClerkOptions, EnvironmentResource, SessionTask } from '@clerk/types';
+
+import { buildURL } from '../utils';
+
+export const SESSION_TASK_ROUTE_BY_KEY: Record<SessionTask['key'], string> = {
+  org: 'add-organization',
+} as const;
+
+interface NavigateToTaskOptions {
+  componentNavigationContext: {
+    navigate: (
+      to: string,
+      options?: {
+        searchParams?: URLSearchParams;
+      },
+    ) => Promise<unknown>;
+    basePath: string;
+  } | null;
+  globalNavigate: (to: string) => Promise<unknown>;
+  options: ClerkOptions;
+  environment: EnvironmentResource;
+}
+
+/**
+ * Handles navigation to the tasks URL based on the application context such
+ * as internal component routing or custom flows.
+ * @internal
+ */
+export function navigateToTask(
+  task: SessionTask,
+  { componentNavigationContext, globalNavigate, options, environment }: NavigateToTaskOptions,
+) {
+  const taskRoute = `/${SESSION_TASK_ROUTE_BY_KEY[task.key]}`;
+
+  if (componentNavigationContext) {
+    return componentNavigationContext.navigate(`/${componentNavigationContext.basePath + taskRoute}`);
+  }
+
+  const signInUrl = options['signInUrl'] || environment.displayConfig.signInUrl;
+  const signUpUrl = options['signUpUrl'] || environment.displayConfig.signUpUrl;
+  const isReferrerSignUpUrl = window.location.href.startsWith(signUpUrl);
+
+  const sessionTaskUrl = buildURL(
+    // TODO - Accept custom URL option for custom flows in order to eject out of `signInUrl/signUpUrl`
+    {
+      base: isReferrerSignUpUrl ? signUpUrl : signInUrl,
+      hashPath: taskRoute,
+    },
+    { stringify: true },
+  );
+
+  return globalNavigate(sessionTaskUrl);
+}
diff --git a/packages/clerk-js/src/core/warnings.ts b/packages/clerk-js/src/core/warnings.ts
index 1fd500a1e91..2513a4d00cc 100644
--- a/packages/clerk-js/src/core/warnings.ts
+++ b/packages/clerk-js/src/core/warnings.ts
@@ -16,8 +16,12 @@ const warnings = {
     'The <SignUp/> and <SignIn/> components cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the Home URL instead.',
   cannotRenderSignUpComponentWhenSessionExists:
     'The <SignUp/> component cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the value set in `afterSignUp` URL instead.',
+  cannotRenderSignUpComponentWhenTaskExists:
+    'The <SignUp/> component cannot render when a user has a pending task, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the task instead.',
   cannotRenderSignInComponentWhenSessionExists:
     'The <SignIn/> component cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the `afterSignIn` URL instead.',
+  cannotRenderSignInComponentWhenTaskExists:
+    'The <SignIn/> component cannot render when a user has a pending task, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the task instead.',
   cannotRenderComponentWhenUserDoesNotExist:
     '<UserProfile/> cannot render unless a user is signed in. Since no user is signed in, this is no-op.',
   cannotRenderComponentWhenOrgDoesNotExist: `<OrganizationProfile/> cannot render unless an organization is active. Since no organization is currently active, this is no-op.`,
diff --git a/packages/clerk-js/src/ui/common/redirects.ts b/packages/clerk-js/src/ui/common/redirects.ts
index 655e5fcefce..6b785c9d694 100644
--- a/packages/clerk-js/src/ui/common/redirects.ts
+++ b/packages/clerk-js/src/ui/common/redirects.ts
@@ -1,3 +1,6 @@
+import type { SessionTask } from '@clerk/types';
+
+import { SESSION_TASK_ROUTE_BY_KEY } from '../../core/sessionTasks';
 import { buildURL } from '../../utils/url';
 import type { SignInContextType, SignUpContextType, UserProfileContextType } from './../contexts';
 
@@ -25,6 +28,28 @@ export function buildVerificationRedirectUrl({
   });
 }
 
+export function buildSessionTaskRedirectUrl({
+  routing,
+  path,
+  baseUrl,
+  task,
+}: Pick<SignInContextType | SignUpContextType, 'routing' | 'path'> & {
+  baseUrl: string;
+  task?: SessionTask;
+}) {
+  if (!task) {
+    return null;
+  }
+
+  return buildRedirectUrl({
+    routing,
+    baseUrl,
+    path,
+    endpoint: `/${SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
+    authQueryString: null,
+  });
+}
+
 export function buildSSOCallbackURL(
   ctx: Partial<SignInContextType | SignUpContextType>,
   baseUrl: string | undefined = '',
diff --git a/packages/clerk-js/src/ui/common/withRedirect.tsx b/packages/clerk-js/src/ui/common/withRedirect.tsx
index 43ec9b95172..81336a63ec2 100644
--- a/packages/clerk-js/src/ui/common/withRedirect.tsx
+++ b/packages/clerk-js/src/ui/common/withRedirect.tsx
@@ -61,8 +61,10 @@ export const withRedirectToAfterSignIn = <P extends AvailableComponentProps>(Com
     return withRedirect(
       Component,
       sessionExistsAndSingleSessionModeEnabled,
-      ({ clerk }) => signInCtx.afterSignInUrl || clerk.buildAfterSignInUrl(),
-      warnings.cannotRenderSignInComponentWhenSessionExists,
+      ({ clerk }) => signInCtx.sessionTaskUrl || signInCtx.afterSignInUrl || clerk.buildAfterSignInUrl(),
+      signInCtx.sessionTaskUrl
+        ? warnings.cannotRenderSignInComponentWhenTaskExists
+        : warnings.cannotRenderSignInComponentWhenSessionExists,
     )(props);
   };
 
@@ -80,8 +82,10 @@ export const withRedirectToAfterSignUp = <P extends AvailableComponentProps>(Com
     return withRedirect(
       Component,
       sessionExistsAndSingleSessionModeEnabled,
-      ({ clerk }) => signUpCtx.afterSignUpUrl || clerk.buildAfterSignUpUrl(),
-      warnings.cannotRenderSignUpComponentWhenSessionExists,
+      ({ clerk }) => signUpCtx.sessionTaskUrl || signUpCtx.afterSignUpUrl || clerk.buildAfterSignUpUrl(),
+      signUpCtx.sessionTaskUrl
+        ? warnings.cannotRenderSignUpComponentWhenTaskExists
+        : warnings.cannotRenderSignUpComponentWhenSessionExists,
     )(props);
   };
 
@@ -89,11 +93,3 @@ export const withRedirectToAfterSignUp = <P extends AvailableComponentProps>(Com
 
   return HOC;
 };
-
-export const withRedirectToHomeSingleSessionGuard = <P extends AvailableComponentProps>(Component: ComponentType<P>) =>
-  withRedirect(
-    Component,
-    sessionExistsAndSingleSessionModeEnabled,
-    ({ environment }) => environment.displayConfig.homeUrl,
-    warnings.cannotRenderComponentWhenSessionExists,
-  );
diff --git a/packages/clerk-js/src/ui/components/SessionTask/SessionTask.tsx b/packages/clerk-js/src/ui/components/SessionTask/SessionTask.tsx
new file mode 100644
index 00000000000..b869d62265e
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/SessionTask/SessionTask.tsx
@@ -0,0 +1,28 @@
+import { useClerk } from '@clerk/shared/react/index';
+import { eventComponentMounted } from '@clerk/shared/telemetry';
+import type { SessionTask } from '@clerk/types';
+
+import { OrganizationListContext } from '../../contexts';
+import { OrganizationList } from '../OrganizationList';
+
+const ContentRegistry: Record<SessionTask['key'], React.ComponentType> = {
+  org: () => (
+    // TODO - Hide personal workspace within organization list context based on environment
+    <OrganizationListContext.Provider value={{ componentName: 'OrganizationList', hidePersonal: true }}>
+      <OrganizationList />
+    </OrganizationListContext.Provider>
+  ),
+};
+
+/**
+ * @internal
+ */
+export function SessionTask({ task }: { task: SessionTask['key'] }): React.ReactNode {
+  const clerk = useClerk();
+
+  clerk.telemetry?.record(eventComponentMounted('SessionTask', { task }));
+
+  const Content = ContentRegistry[task];
+
+  return <Content />;
+}
diff --git a/packages/clerk-js/src/ui/components/SessionTask/index.ts b/packages/clerk-js/src/ui/components/SessionTask/index.ts
new file mode 100644
index 00000000000..ddae613a6e2
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/SessionTask/index.ts
@@ -0,0 +1 @@
+export * from './SessionTask';
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
index c30b60ed5a0..2a5895836f0 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
@@ -2,6 +2,7 @@ import { useClerk } from '@clerk/shared/react';
 import type { SignInModalProps, SignInProps } from '@clerk/types';
 import React from 'react';
 
+import { SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
 import { normalizeRoutingOptions } from '../../../utils/normalizeRoutingOptions';
 import { SignInEmailLinkFlowComplete, SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
 import type { SignUpContextType } from '../../contexts';
@@ -14,7 +15,8 @@ import {
 } from '../../contexts';
 import { Flow } from '../../customizables';
 import { useFetch } from '../../hooks';
-import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
+import { preloadSessionTask, SessionTask } from '../../lazyModules/components';
+import { Route, Switch, useRouter, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
 import {
   LazySignUpContinue,
   LazySignUpSSOCallback,
@@ -128,6 +130,11 @@ function SignInRoutes(): JSX.Element {
               >
                 <LazySignUpVerifyPhone />
               </Route>
+              {signInContext.withSessionTasks && (
+                <Route path={SESSION_TASK_ROUTE_BY_KEY['org']}>
+                  <SessionTask task='org' />
+                </Route>
+              )}
               <Route index>
                 <LazySignUpContinue />
               </Route>
@@ -137,7 +144,11 @@ function SignInRoutes(): JSX.Element {
             </Route>
           </Route>
         )}
-
+        {signInContext.withSessionTasks && (
+          <Route path={SESSION_TASK_ROUTE_BY_KEY['org']}>
+            <SessionTask task='org' />
+          </Route>
+        )}
         <Route index>
           <SignInStart />
         </Route>
@@ -152,7 +163,13 @@ function SignInRoutes(): JSX.Element {
 const usePreloadSignUp = (enabled = false) =>
   useFetch(enabled ? preloadSignUp : undefined, 'preloadComponent', { staleTime: Infinity });
 
+const usePreloadSessionTask = (enabled = false) =>
+  useFetch(enabled ? preloadSessionTask : undefined, 'preloadComponent', { staleTime: Infinity });
+
 function SignInRoot() {
+  const { __internal_setComponentNavigationContext } = useClerk();
+  const { navigate, basePath } = useRouter();
+
   const signInContext = useSignInContext();
   const normalizedSignUpContext = {
     componentName: 'SignUp',
@@ -170,6 +187,13 @@ function SignInRoot() {
    */
   usePreloadSignUp(signInContext.isCombinedFlow);
 
+  // `experimental.withSessionTasks` will be removed soon in favor of checking via environment response
+  usePreloadSessionTask(signInContext.withSessionTasks);
+
+  React.useEffect(() => {
+    return __internal_setComponentNavigationContext?.({ basePath, navigate });
+  }, [basePath, navigate]);
+
   return (
     <SignUpContext.Provider value={normalizedSignUpContext}>
       <SignInRoutes />
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUp.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUp.tsx
index c96abac836b..9e42f7efac8 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUp.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUp.tsx
@@ -2,16 +2,22 @@ import { useClerk } from '@clerk/shared/react';
 import type { SignUpModalProps, SignUpProps } from '@clerk/types';
 import React from 'react';
 
+import { SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
 import { SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
 import { SignUpContext, useSignUpContext, withCoreSessionSwitchGuard } from '../../contexts';
 import { Flow } from '../../customizables';
-import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
+import { useFetch } from '../../hooks';
+import { preloadSessionTask, SessionTask } from '../../lazyModules/components';
+import { Route, Switch, useRouter, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
 import { SignUpContinue } from './SignUpContinue';
 import { SignUpSSOCallback } from './SignUpSSOCallback';
 import { SignUpStart } from './SignUpStart';
 import { SignUpVerifyEmail } from './SignUpVerifyEmail';
 import { SignUpVerifyPhone } from './SignUpVerifyPhone';
 
+const usePreloadSessionTask = (enabled = false) =>
+  useFetch(enabled ? preloadSessionTask : undefined, 'preloadComponent', { staleTime: Infinity });
+
 function RedirectToSignUp() {
   const clerk = useClerk();
   React.useEffect(() => {
@@ -21,8 +27,17 @@ function RedirectToSignUp() {
 }
 
 function SignUpRoutes(): JSX.Element {
+  const { __internal_setComponentNavigationContext } = useClerk();
+  const { navigate, basePath } = useRouter();
   const signUpContext = useSignUpContext();
 
+  // `experimental.withSessionTasks` will be removed soon in favor of checking via environment response
+  usePreloadSessionTask(signUpContext.withSessionTasks);
+
+  React.useEffect(() => {
+    return __internal_setComponentNavigationContext?.({ basePath, navigate });
+  }, [basePath, navigate]);
+
   return (
     <Flow.Root flow='signUp'>
       <Switch>
@@ -74,6 +89,11 @@ function SignUpRoutes(): JSX.Element {
             <SignUpContinue />
           </Route>
         </Route>
+        {signUpContext.withSessionTasks && (
+          <Route path={SESSION_TASK_ROUTE_BY_KEY['org']}>
+            <SessionTask task='org' />
+          </Route>
+        )}
         <Route index>
           <SignUpStart />
         </Route>
diff --git a/packages/clerk-js/src/ui/contexts/components/SignIn.ts b/packages/clerk-js/src/ui/contexts/components/SignIn.ts
index 564f755992c..d709a0d14b4 100644
--- a/packages/clerk-js/src/ui/contexts/components/SignIn.ts
+++ b/packages/clerk-js/src/ui/contexts/components/SignIn.ts
@@ -5,7 +5,12 @@ import { createContext, useContext, useMemo } from 'react';
 import { SIGN_IN_INITIAL_VALUE_KEYS } from '../../../core/constants';
 import { buildURL } from '../../../utils';
 import { RedirectUrls } from '../../../utils/redirectUrls';
-import { buildRedirectUrl, MAGIC_LINK_VERIFY_PATH_ROUTE, SSO_CALLBACK_PATH_ROUTE } from '../../common/redirects';
+import {
+  buildRedirectUrl,
+  buildSessionTaskRedirectUrl,
+  MAGIC_LINK_VERIFY_PATH_ROUTE,
+  SSO_CALLBACK_PATH_ROUTE,
+} from '../../common/redirects';
 import { useEnvironment, useOptions } from '../../contexts';
 import type { ParsedQueryString } from '../../router';
 import { useRouter } from '../../router';
@@ -21,11 +26,13 @@ export type SignInContextType = SignInCtx & {
   authQueryString: string | null;
   afterSignUpUrl: string;
   afterSignInUrl: string;
+  sessionTaskUrl: string | null;
   transferable: boolean;
   waitlistUrl: string;
   emailLinkRedirectUrl: string;
   ssoCallbackUrl: string;
   isCombinedFlow: boolean;
+  withSessionTasks: boolean;
 };
 
 export const SignInContext = createContext<SignInCtx | null>(null);
@@ -112,6 +119,13 @@ export const useSignInContext = (): SignInContextType => {
 
   const signUpContinueUrl = buildURL({ base: signUpUrl, hashPath: '/continue' }, { stringify: true });
 
+  const sessionTaskUrl = buildSessionTaskRedirectUrl({
+    task: clerk.session?.currentTask,
+    path: ctx.path,
+    routing: ctx.routing,
+    baseUrl: signInUrl,
+  });
+
   return {
     ...(ctx as SignInCtx),
     transferable: ctx.transferable ?? true,
@@ -123,11 +137,13 @@ export const useSignInContext = (): SignInContextType => {
     afterSignUpUrl,
     emailLinkRedirectUrl,
     ssoCallbackUrl,
+    sessionTaskUrl,
     navigateAfterSignIn,
     signUpContinueUrl,
     queryParams,
     initialValues: { ...ctx.initialValues, ...initialValuesFromQueryParams },
     authQueryString,
     isCombinedFlow,
+    withSessionTasks: !!options.experimental?.withSessionTasks,
   };
 };
diff --git a/packages/clerk-js/src/ui/contexts/components/SignUp.ts b/packages/clerk-js/src/ui/contexts/components/SignUp.ts
index 32b4e4794c0..59ce5f4ab09 100644
--- a/packages/clerk-js/src/ui/contexts/components/SignUp.ts
+++ b/packages/clerk-js/src/ui/contexts/components/SignUp.ts
@@ -5,7 +5,12 @@ import { createContext, useContext, useMemo } from 'react';
 import { SIGN_UP_INITIAL_VALUE_KEYS } from '../../../core/constants';
 import { buildURL } from '../../../utils';
 import { RedirectUrls } from '../../../utils/redirectUrls';
-import { buildRedirectUrl, MAGIC_LINK_VERIFY_PATH_ROUTE, SSO_CALLBACK_PATH_ROUTE } from '../../common/redirects';
+import {
+  buildRedirectUrl,
+  buildSessionTaskRedirectUrl,
+  MAGIC_LINK_VERIFY_PATH_ROUTE,
+  SSO_CALLBACK_PATH_ROUTE,
+} from '../../common/redirects';
 import { useEnvironment, useOptions } from '../../contexts';
 import type { ParsedQueryString } from '../../router';
 import { useRouter } from '../../router';
@@ -22,9 +27,11 @@ export type SignUpContextType = SignUpCtx & {
   afterSignUpUrl: string;
   afterSignInUrl: string;
   waitlistUrl: string;
+  sessionTaskUrl: string | null;
   isCombinedFlow: boolean;
   emailLinkRedirectUrl: string;
   ssoCallbackUrl: string;
+  withSessionTasks: boolean;
 };
 
 export const SignUpContext = createContext<SignUpCtx | null>(null);
@@ -107,6 +114,13 @@ export const useSignUpContext = (): SignUpContextType => {
   // TODO: Avoid building this url again to remove duplicate code. Get it from window.Clerk instead.
   const secondFactorUrl = buildURL({ base: signInUrl, hashPath: '/factor-two' }, { stringify: true });
 
+  const sessionTaskUrl = buildSessionTaskRedirectUrl({
+    task: clerk.session?.currentTask,
+    path: ctx.path,
+    routing: ctx.routing,
+    baseUrl: signUpUrl,
+  });
+
   return {
     ...ctx,
     componentName,
@@ -118,10 +132,12 @@ export const useSignUpContext = (): SignUpContextType => {
     afterSignInUrl,
     emailLinkRedirectUrl,
     ssoCallbackUrl,
+    sessionTaskUrl,
     navigateAfterSignUp,
     queryParams,
     initialValues: { ...ctx.initialValues, ...initialValuesFromQueryParams },
     authQueryString,
     isCombinedFlow,
+    withSessionTasks: !!options.experimental?.withSessionTasks,
   };
 };
diff --git a/packages/clerk-js/src/ui/lazyModules/components.ts b/packages/clerk-js/src/ui/lazyModules/components.ts
index 51788d64910..6182576c716 100644
--- a/packages/clerk-js/src/ui/lazyModules/components.ts
+++ b/packages/clerk-js/src/ui/lazyModules/components.ts
@@ -19,6 +19,7 @@ const componentImportPaths = {
   KeylessPrompt: () => import(/* webpackChunkName: "keylessPrompt" */ '../components/KeylessPrompt'),
   PricingTable: () => import(/* webpackChunkName: "pricingTable" */ '../components/PricingTable'),
   Checkout: () => import(/* webpackChunkName: "checkout" */ '../components/Checkout'),
+  SessionTask: () => import(/* webpackChunkName: "sessionTask" */ '../components/SessionTask'),
 } as const;
 
 export const SignIn = lazy(() => componentImportPaths.SignIn().then(module => ({ default: module.SignIn })));
@@ -94,6 +95,11 @@ export const PricingTable = lazy(() =>
   componentImportPaths.PricingTable().then(module => ({ default: module.__experimental_PricingTable })),
 );
 
+export const preloadSessionTask = () => import(/* webpackChunkName: "sessionTask" */ '../components/SessionTask');
+export const SessionTask = lazy(() =>
+  componentImportPaths.SessionTask().then(module => ({ default: module.SessionTask })),
+);
+
 export const preloadComponent = async (component: unknown) => {
   return componentImportPaths[component as keyof typeof componentImportPaths]?.();
 };
diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts
index 455b9a72aa8..c2b85093152 100644
--- a/packages/react/src/isomorphicClerk.ts
+++ b/packages/react/src/isomorphicClerk.ts
@@ -93,6 +93,7 @@ type IsomorphicLoadedClerk = Without<
   | '__internal_getCachedResources'
   | '__internal_reloadInitialResources'
   | '__experimental_commerce'
+  | '__internal_setComponentNavigationContext'
 > & {
   client: ClientResource | undefined;
   __experimental_commerce: __experimental_CommerceNamespace | undefined;
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 29834688505..e1366319454 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -426,6 +426,21 @@ export interface Clerk {
    */
   __internal_addNavigationListener: (callback: () => void) => UnsubscribeCallback;
 
+  /**
+   * Registers the internal navigation context from UI components in order to
+   * be triggered from `Clerk` methods
+   * @internal
+   */
+  __internal_setComponentNavigationContext: (context: {
+    navigate: (
+      to: string,
+      options?: {
+        searchParams?: URLSearchParams;
+      },
+    ) => Promise<unknown>;
+    basePath: string;
+  }) => () => void;
+
   /**
    * Set the active session and organization explicitly.
    *
@@ -803,6 +818,8 @@ export type ClerkOptions = ClerkOptionsNavigation &
          */
         rethrowOfflineNetworkErrors: boolean;
         commerce: boolean;
+        // `experimental.withSessionTasks` will be removed soon in favor of checking via environment response
+        withSessionTasks: boolean;
       },
       Record<string, any>
     >;
diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts
index 6f40a118250..bd48545b462 100644
--- a/packages/types/src/session.ts
+++ b/packages/types/src/session.ts
@@ -124,6 +124,7 @@ export interface SessionResource extends ClerkResource {
   lastActiveAt: Date;
   actor: ActJWTClaim | null;
   tasks: Array<SessionTask> | null;
+  currentTask?: SessionTask;
   /**
    * The user associated with the session.
    */
@@ -173,6 +174,7 @@ export interface ActiveSessionResource extends SessionResource {
 export interface PendingSessionResource extends SessionResource {
   status: 'pending';
   user: UserResource;
+  currentTask: SessionTask;
 }
 
 /**
@@ -224,7 +226,7 @@ export interface PublicUserData {
 }
 
 export interface SessionTask {
-  key: 'orgs';
+  key: 'org';
 }
 
 export type GetTokenOptions = {