diff --git a/.github/workflows/release-embedding-sdk.yml b/.github/workflows/release-embedding-sdk.yml
index cccd93f345bacded0604d690fec929e3f33d2288..044c50f207322d3951408f6797d0b68fe9824cf1 100644
--- a/.github/workflows/release-embedding-sdk.yml
+++ b/.github/workflows/release-embedding-sdk.yml
@@ -89,6 +89,7 @@ jobs:
           ./bin/embedding-sdk/release_utils.bash update_readme ${{ inputs.sdk_version }}
 
       - name: Bump published npm package version
+        # NOTE: this should happen before "Build SDK bundle" as we inject SDK version into the code during build step
         run: |
           ./bin/embedding-sdk/release_utils.bash update_package_json_template ${{ inputs.sdk_version }}
 
diff --git a/.storybook/main.js b/.storybook/main.js
index 597f8cfe5ea52087f25931bc55f38bcb62a6a407..58b21300a164e6e3436cf2f87809f34bf0b7bb3e 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -1,6 +1,8 @@
 const webpack = require("webpack");
 const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 const appConfig = require("../webpack.config");
+const fs = require("fs");
+const path = require("path");
 
 const isEmbeddingSDK = process.env.IS_EMBEDDING_SDK === "true";
 
@@ -13,6 +15,15 @@ const embeddingSdkStories = [
   "../enterprise/frontend/src/embedding-sdk/**/*.stories.tsx",
 ];
 
+const sdkPackageTemplateJson = fs.readFileSync(
+  path.resolve("./enterprise/frontend/src/embedding-sdk/package.template.json"),
+  "utf-8",
+);
+const sdkPackageTemplateJsonContent = JSON.parse(sdkPackageTemplateJson);
+const EMBEDDING_SDK_VERSION = JSON.stringify(
+  sdkPackageTemplateJsonContent.version,
+);
+
 module.exports = {
   core: {
     builder: "webpack5",
@@ -36,6 +47,9 @@ module.exports = {
       new webpack.ProvidePlugin({
         Buffer: ["buffer", "Buffer"],
       }),
+      new webpack.EnvironmentPlugin({
+        EMBEDDING_SDK_VERSION,
+      }),
     ],
     module: {
       ...storybookConfig.module,
diff --git a/e2e/support/helpers/e2e-embedding-helpers.js b/e2e/support/helpers/e2e-embedding-helpers.js
index 36a16603d92b0f2afb51fd2e19229716cb38ff66..a6fe8a95f6a5f63fbd423eeaf94655202a31ede2 100644
--- a/e2e/support/helpers/e2e-embedding-helpers.js
+++ b/e2e/support/helpers/e2e-embedding-helpers.js
@@ -38,7 +38,7 @@ import { modal, popover } from "e2e/support/helpers/e2e-ui-elements-helpers";
  */
 export function visitEmbeddedPage(
   payload,
-  { setFilters = {}, hideFilters = [], pageStyle = {} } = {},
+  { setFilters = {}, hideFilters = [], pageStyle = {}, onBeforeLoad } = {},
 ) {
   const jwtSignLocation = "e2e/support/external/e2e-jwt-sign.js";
 
@@ -63,6 +63,7 @@ export function visitEmbeddedPage(
       url: urlRoot,
       qs: setFilters,
       onBeforeLoad: window => {
+        onBeforeLoad?.(window);
         if (urlHash) {
           window.location.hash = urlHash;
         }
diff --git a/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js b/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js
index f034e4ee6208c6ed5e363111153facaa834e8161..fa6df526eee6082607397052732398fc094bb8d3 100644
--- a/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js
+++ b/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js
@@ -538,6 +538,34 @@ describe("scenarios > embedding > dashboard parameters", () => {
     );
     dismissDownloadStatus();
   });
+
+  it("should send 'X-Metabase-Client' header for api requests", () => {
+    cy.intercept("GET", "api/embed/dashboard/*").as("getEmbeddedDashboard");
+
+    cy.get("@dashboardId").then(dashboardId => {
+      cy.request("PUT", `/api/dashboard/${dashboardId}`, {
+        embedding_params: {},
+        enable_embedding: true,
+      });
+
+      const payload = {
+        resource: { dashboard: dashboardId },
+        params: {},
+      };
+
+      visitEmbeddedPage(payload, {
+        onBeforeLoad: window => {
+          window.Cypress = undefined;
+        },
+      });
+
+      cy.wait("@getEmbeddedDashboard").then(({ request }) => {
+        expect(request?.headers?.["x-metabase-client"]).to.equal(
+          "embedding-iframe",
+        );
+      });
+    });
+  });
 });
 
 describe("scenarios > embedding > dashboard parameters with defaults", () => {
diff --git a/e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js b/e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
index 5804119f1d6cc107938f6e4cfcd8bea293433d5e..3b10f75768014eb26bf4ceb460e17e6cd41a80e5 100644
--- a/e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
+++ b/e2e/test/scenarios/embedding/interactive-embedding.cy.spec.js
@@ -117,7 +117,7 @@ describeEE("scenarios > embedding > full app", () => {
       sideNav().should("not.exist");
     });
 
-    it("should disable home link when top nav is enabeld but side nav is disabled", () => {
+    it("should disable home link when top nav is enabled but side nav is disabled", () => {
       visitDashboardUrl({
         url: `/dashboard/${ORDERS_DASHBOARD_ID}`,
         qs: { top_nav: true, side_nav: false },
@@ -226,6 +226,19 @@ describeEE("scenarios > embedding > full app", () => {
       cy.button("Filter").should("not.exist");
     });
 
+    it("should send 'X-Metabase-Client' header for api requests", () => {
+      visitFullAppEmbeddingUrl({
+        url: "/question/" + ORDERS_QUESTION_ID,
+        qs: { action_buttons: false },
+      });
+
+      cy.wait("@getCardQuery").then(({ request }) => {
+        expect(request?.headers?.["x-metabase-client"]).to.equal(
+          "embedding-iframe",
+        );
+      });
+    });
+
     describe("question creation", () => {
       beforeEach(() => {
         cy.signOut();
@@ -559,6 +572,16 @@ describeEE("scenarios > embedding > full app", () => {
         ).to.equal(CSRF_TOKEN);
       });
     });
+
+    it("should send 'X-Metabase-Client' header for api requests", () => {
+      visitFullAppEmbeddingUrl({ url: `/dashboard/${ORDERS_DASHBOARD_ID}` });
+
+      cy.wait("@getDashboard").then(({ request }) => {
+        expect(request?.headers?.["x-metabase-client"]).to.equal(
+          "embedding-iframe",
+        );
+      });
+    });
   });
 
   describe("x-ray dashboards", () => {
diff --git a/enterprise/frontend/src/embedding-sdk/config.ts b/enterprise/frontend/src/embedding-sdk/config.ts
index 68d9c718a5d170210b72a57739d93ef3b3014b3e..a80fe74f6560ecdb7df4694942ba4d606319d1e2 100644
--- a/enterprise/frontend/src/embedding-sdk/config.ts
+++ b/enterprise/frontend/src/embedding-sdk/config.ts
@@ -1,2 +1,5 @@
 export const DEFAULT_FONT = "Lato";
 export const EMBEDDING_SDK_ROOT_ELEMENT_ID = "metabase-sdk-root";
+
+export const getEmbeddingSdkVersion = () =>
+  process.env.EMBEDDING_SDK_VERSION ?? "unknown";
diff --git a/enterprise/frontend/src/embedding-sdk/hooks/private/use-init-data.ts b/enterprise/frontend/src/embedding-sdk/hooks/private/use-init-data.ts
index e6e95661a40fae903b6d99e6ecf9e7d698502bde..9cc8d9f65145c6fc83063dd883ae23d8e5f610c8 100644
--- a/enterprise/frontend/src/embedding-sdk/hooks/private/use-init-data.ts
+++ b/enterprise/frontend/src/embedding-sdk/hooks/private/use-init-data.ts
@@ -1,6 +1,7 @@
 import { useEffect } from "react";
 import _ from "underscore";
 
+import { getEmbeddingSdkVersion } from "embedding-sdk/config";
 import { getAuthConfiguration } from "embedding-sdk/hooks/private/get-auth-configuration";
 import { getErrorMessage } from "embedding-sdk/lib/user-warnings/constants";
 import { useSdkDispatch, useSdkSelector } from "embedding-sdk/store";
@@ -31,7 +32,19 @@ export const useInitData = ({ config }: InitDataLoaderParameters) => {
 
   useEffect(() => {
     registerVisualizationsOnce();
-  }, [dispatch]);
+
+    const EMBEDDING_SDK_VERSION = getEmbeddingSdkVersion();
+    api.requestClient = {
+      name: "embedding-sdk-react",
+      version: EMBEDDING_SDK_VERSION,
+    };
+
+    // eslint-disable-next-line no-console
+    console.log(
+      // eslint-disable-next-line no-literal-metabase-strings -- Not a user facing string
+      `Using Metabase Embedding SDK, version "${EMBEDDING_SDK_VERSION}"`,
+    );
+  }, []);
 
   useEffect(() => {
     dispatch(setFetchRefreshTokenFn(config.fetchRequestToken ?? null));
diff --git a/enterprise/frontend/src/embedding-sdk/hooks/private/use-init-data.unit.spec.tsx b/enterprise/frontend/src/embedding-sdk/hooks/private/use-init-data.unit.spec.tsx
index f371a6a2d6612569904b274ecfcf3b8b1988fb6b..a8f10a2cb5eb5607adce83b5d6302eec9b6b2a36 100644
--- a/enterprise/frontend/src/embedding-sdk/hooks/private/use-init-data.unit.spec.tsx
+++ b/enterprise/frontend/src/embedding-sdk/hooks/private/use-init-data.unit.spec.tsx
@@ -1,4 +1,5 @@
-import { act } from "@testing-library/react";
+import { waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
 import fetchMock from "fetch-mock";
 
 import { setupEnterprisePlugins } from "__support__/enterprise";
@@ -9,6 +10,7 @@ import {
 } from "__support__/server-mocks";
 import { mockSettings } from "__support__/settings";
 import { renderWithProviders, screen } from "__support__/ui";
+import * as sdkConfigModule from "embedding-sdk/config";
 import { useInitData } from "embedding-sdk/hooks";
 import { sdkReducers, useSdkSelector } from "embedding-sdk/store";
 import { refreshTokenAsync } from "embedding-sdk/store/reducer";
@@ -20,6 +22,7 @@ import {
   createMockSdkState,
 } from "embedding-sdk/test/mocks/state";
 import type { SDKConfig, SDKConfigWithJWT } from "embedding-sdk/types";
+import { GET } from "metabase/lib/api";
 import { useDispatch } from "metabase/lib/redux";
 import {
   createMockSettings,
@@ -46,6 +49,10 @@ const TestComponent = ({ config }: { config: SDKConfig }) => {
   const refreshToken = () =>
     dispatch(refreshTokenAsync("http://TEST_URI/sso/metabase"));
 
+  const handleClick = () => {
+    GET("/api/some/url")();
+  };
+
   return (
     <div
       data-testid="test-component"
@@ -55,6 +62,7 @@ const TestComponent = ({ config }: { config: SDKConfig }) => {
     >
       Test Component
       <button onClick={refreshToken}>Refresh Token</button>
+      <button onClick={handleClick}>Send test request</button>
     </div>
   );
 };
@@ -75,6 +83,8 @@ const setup = ({
     iat: 1965805007,
   });
 
+  fetchMock.get("path:/api/some/url", {});
+
   setupCurrentUserEndpoint(
     TEST_USER,
     isValidUser
@@ -131,6 +141,29 @@ describe("useInitData hook", () => {
         "No JWT URI or API key provided.",
       );
     });
+
+    it("should set a context for all API requests", async () => {
+      jest
+        .spyOn(sdkConfigModule, "getEmbeddingSdkVersion")
+        .mockImplementationOnce(() => "1.2.3");
+
+      setup({});
+
+      await userEvent.click(screen.getByText("Send test request"));
+
+      await waitFor(() => {
+        expect(fetchMock.called("path:/api/some/url")).toBeTruthy();
+      });
+
+      const lastCallRequest = fetchMock.lastCall("path:/api/some/url")?.request;
+
+      expect(lastCallRequest?.headers.get("X-Metabase-Client")).toEqual(
+        "embedding-sdk-react",
+      );
+      expect(lastCallRequest?.headers.get("X-Metabase-Client-Version")).toEqual(
+        "1.2.3",
+      );
+    });
   });
 
   describe("JWT authentication", () => {
@@ -204,9 +237,7 @@ describe("useInitData hook", () => {
 
       rerender(<TestComponent config={config} />);
 
-      act(() => {
-        screen.getByText("Refresh Token").click();
-      });
+      await userEvent.click(screen.getByText("Refresh Token"));
 
       expect(fetchRequestToken).toHaveBeenCalledTimes(1);
     });
diff --git a/enterprise/frontend/src/embedding-sdk/types/globalTypes.d.ts b/enterprise/frontend/src/embedding-sdk/types/globalTypes.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9f6f17272e5f725dec49df93b3ce8a7e4fc3789e
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/types/globalTypes.d.ts
@@ -0,0 +1,3 @@
+interface Window {
+  EMBEDDING_SDK_VERSION?: string;
+}
diff --git a/frontend/src/metabase/lib/api.js b/frontend/src/metabase/lib/api.js
index ca6a4c2d798467b36a2219fefde134275b266d94..136d60db7203b49b47560dac3e0bbb0663639d70 100644
--- a/frontend/src/metabase/lib/api.js
+++ b/frontend/src/metabase/lib/api.js
@@ -36,6 +36,11 @@ export class Api extends EventEmitter {
 
   onBeforeRequest;
 
+  /**
+   * @type {string|{name: string, version: string}}
+   */
+  requestClient;
+
   GET;
   POST;
   PUT;
@@ -50,6 +55,8 @@ export class Api extends EventEmitter {
   }
 
   _makeMethod(method, creatorOptions = {}) {
+    const self = this;
+
     return (urlTemplate, methodOptions = {}) => {
       if (typeof methodOptions === "function") {
         methodOptions = { transformResponse: methodOptions };
@@ -102,13 +109,27 @@ export class Api extends EventEmitter {
         }
 
         if (this.sessionToken) {
-          // eslint-disable-next-line no-literal-metabase-strings -- not a UI string
+          // eslint-disable-next-line no-literal-metabase-strings -- Not a user facing string
           headers["X-Metabase-Session"] = this.sessionToken;
         }
 
         if (isWithinIframe()) {
           // eslint-disable-next-line no-literal-metabase-strings -- Not a user facing string
           headers["X-Metabase-Embedded"] = "true";
+          // eslint-disable-next-line no-literal-metabase-strings -- Not a user facing string
+          headers["X-Metabase-Client"] = "embedding-iframe";
+        }
+
+        if (self.requestClient) {
+          if (typeof self.requestClient === "object") {
+            // eslint-disable-next-line no-literal-metabase-strings -- Not a user facing string
+            headers["X-Metabase-Client"] = self.requestClient.name;
+            // eslint-disable-next-line no-literal-metabase-strings -- Not a user facing string
+            headers["X-Metabase-Client-Version"] = self.requestClient.version;
+          } else {
+            // eslint-disable-next-line no-literal-metabase-strings -- Not a user facing string
+            headers["X-Metabase-Client"] = self.requestClient;
+          }
         }
 
         if (ANTI_CSRF_TOKEN) {
diff --git a/webpack.embedding-sdk.config.js b/webpack.embedding-sdk.config.js
index f13fe1cc51fb1021836d81e73eb8f0aa535b8ec6..dc71228f852bf598f5f03552cc81b2d2126e2660 100644
--- a/webpack.embedding-sdk.config.js
+++ b/webpack.embedding-sdk.config.js
@@ -10,6 +10,8 @@ const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
 
 const mainConfig = require("./webpack.config");
 const { resolve } = require("path");
+const fs = require("fs");
+const path = require("path");
 
 const SDK_SRC_PATH = __dirname + "/enterprise/frontend/src/embedding-sdk";
 const BUILD_PATH = __dirname + "/resources/embedding-sdk";
@@ -20,6 +22,15 @@ const ENTERPRISE_SRC_PATH =
 const WEBPACK_BUNDLE = process.env.WEBPACK_BUNDLE || "development";
 const isDevMode = WEBPACK_BUNDLE !== "production";
 
+const sdkPackageTemplateJson = fs.readFileSync(
+  path.resolve("./enterprise/frontend/src/embedding-sdk/package.template.json"),
+  "utf-8",
+);
+const sdkPackageTemplateJsonContent = JSON.parse(sdkPackageTemplateJson);
+const EMBEDDING_SDK_VERSION = JSON.stringify(
+  sdkPackageTemplateJsonContent.version,
+);
+
 // TODO: Reuse babel and css configs from webpack.config.js
 // Babel:
 const BABEL_CONFIG = {
@@ -136,7 +147,9 @@ module.exports = env => {
       new webpack.ProvidePlugin({
         process: "process/browser.js",
       }),
-
+      new webpack.EnvironmentPlugin({
+        EMBEDDING_SDK_VERSION,
+      }),
       new ForkTsCheckerWebpackPlugin({
         async: isDevMode,
         typescript: {
diff --git a/webpack.static-viz.config.js b/webpack.static-viz.config.js
index 8fac3ca092d844ceb40b839520e692070b887542..b9ba90c2776a0dfa852caf6dd9815eadd1a35432 100644
--- a/webpack.static-viz.config.js
+++ b/webpack.static-viz.config.js
@@ -1,5 +1,6 @@
 const YAML = require("json-to-pretty-yaml");
 const TerserPlugin = require("terser-webpack-plugin");
+const webpack = require("webpack");
 const { StatsWriterPlugin } = require("webpack-stats-plugin");
 
 const ASSETS_PATH = __dirname + "/resources/frontend_client/app/assets";