From 98680c4ba8808ed2783b74e85f266d93535d8555 Mon Sep 17 00:00:00 2001 From: Georgis Andonis Date: Fri, 24 Jul 2026 18:31:00 +0300 Subject: [PATCH 01/10] docs(mcp-analytics): note Python session tokens on stateless servers The stateless section's Python note said session tokens were TypeScript-only. They work in Python too: instrument() auto-wires the mint into FastMCP's streamable_http_app()/sse_app() factories (stateless_http), with no enableJsonResponse equivalent (mints from an ASGI layer; JSON + SSE). Replace the FYI callout with one concise line. Generated-By: PostHog Code Task-Id: f44ec5e0-b836-4d13-98c4-c26887dd7ec2 --- contents/docs/mcp-analytics/installation.mdx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contents/docs/mcp-analytics/installation.mdx b/contents/docs/mcp-analytics/installation.mdx index b9881919038d..3a53b3c45646 100644 --- a/contents/docs/mcp-analytics/installation.mdx +++ b/contents/docs/mcp-analytics/installation.mdx @@ -219,11 +219,7 @@ if (body?.method === "initialize" && !req.headers[MCP_SESSION_HEADER]) { Some frameworks construct the transport for you and don't expose `enableJsonResponse`, and a client that ignores the header falls back to a generated session per request either way. In both cases, group by user with [`identify`](/docs/mcp-analytics/identifying-users) — `distinct_id` groups a person's calls however many requests they span, and it requires nothing from the client. [Conversation IDs](/docs/mcp-analytics/conversation-id) give finer, per-conversation grouping when the agent cooperates. - - -Session tokens are TypeScript-only today. A Python server on a stateless deployment gets a session per request — group with `identify` as above. - - +In **Python**, `instrument()` handles this automatically on a stateless `FastMCP(stateless_http=True)` server, with no `enableJsonResponse` equivalent to set (it mints from an ASGI layer, so JSON and SSE both work). A custom `PostHogMCP` dispatcher adds [`PostHogMcpStatelessSessionMiddleware`](/docs/mcp-analytics/custom-servers). ## Python From 06d50fe236e6035389216c2ab55a77521ff59602 Mon Sep 17 00:00:00 2001 From: Georgis Andonis Date: Fri, 24 Jul 2026 18:44:51 +0300 Subject: [PATCH 02/10] docs(mcp-analytics): stateless note for the PostHogMCP custom dispatcher Add a "Stateless / multi-pod dispatchers" subsection to the Python part of Custom servers: add PostHogMcpStatelessSessionMiddleware to your ASGI app and feed get_mcp_session() into the capture_* calls so $session_id + the client harness stay consistent across pods. Generated-By: PostHog Code Task-Id: f44ec5e0-b836-4d13-98c4-c26887dd7ec2 --- .../docs/mcp-analytics/custom-servers.mdx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/contents/docs/mcp-analytics/custom-servers.mdx b/contents/docs/mcp-analytics/custom-servers.mdx index e6dbe60d171d..328cc0589680 100644 --- a/contents/docs/mcp-analytics/custom-servers.mdx +++ b/contents/docs/mcp-analytics/custom-servers.mdx @@ -147,3 +147,25 @@ posthog.flush() # PostHogMCP is a posthog client — flush/shutdown it yourself ``` `PostHogMCP(api_key, missing_capability_tool_name="get_more_tools", mcp_exception_autocapture=True, **posthog_kwargs)` accepts the standard `posthog` client kwargs (e.g. `host`). Set `mcp_exception_autocapture=False` to stop a failed tool call from emitting a `$exception` sibling. As in TypeScript, the wrapping-path hooks (`identify`, `context`, `intent_fallback`, `event_properties`) don't apply here — pass identity and properties on each `capture_*` call. + +### Stateless / multi-pod dispatchers + +On a stateless deployment (a fresh server per request, often across pods) there's no connection to carry a session, so `$session_id` fragments and the client name/version — sent only at `initialize` — go missing from later requests. Add the mint middleware to your ASGI app once. It mints a self-encoded token onto the `Mcp-Session-Id` response header at `initialize` and decodes the client's replay on every later request, so every pod recovers the same values with no shared store: + +```python +from posthog.mcp import PostHogMcpStatelessSessionMiddleware, get_mcp_session + +app.add_middleware(PostHogMcpStatelessSessionMiddleware) + +# ...then in your request handler, feed the recovered session into each capture: +sess = get_mcp_session(request) # None until the client replays the token +posthog.capture_tool_call( + name, + session_id=sess.session_id if sess else None, + client_name=sess.client_name if sess else None, + client_version=sess.client_version if sess else None, + intent=prepared.intent, +) +``` + +The token is unsigned and carries only what the client volunteered at `initialize` — treat `$session_id` and `$mcp_client_*` as analytics labels, not authentication. From a0aaed7ec53d41aa194d0741715563a71ea3cafb Mon Sep 17 00:00:00 2001 From: Georgis Andonis Date: Mon, 27 Jul 2026 13:38:44 +0300 Subject: [PATCH 03/10] docs(mcp-analytics): fix capture_tool_call kwargs in stateless example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit capture_tool_call has no client_name/client_version params — the recovered client identity goes via $mcp_client_* properties (matching the TS example in the same doc). Verified against the published posthog 7.30.0. Generated-By: PostHog Code Task-Id: f44ec5e0-b836-4d13-98c4-c26887dd7ec2 --- contents/docs/mcp-analytics/custom-servers.mdx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contents/docs/mcp-analytics/custom-servers.mdx b/contents/docs/mcp-analytics/custom-servers.mdx index 328cc0589680..07e23d00cf6c 100644 --- a/contents/docs/mcp-analytics/custom-servers.mdx +++ b/contents/docs/mcp-analytics/custom-servers.mdx @@ -157,14 +157,18 @@ from posthog.mcp import PostHogMcpStatelessSessionMiddleware, get_mcp_session app.add_middleware(PostHogMcpStatelessSessionMiddleware) -# ...then in your request handler, feed the recovered session into each capture: +# ...then in your request handler, feed the recovered session into each capture. +# The token carries the client identity too — pass it as $mcp_client_* properties +# (capture_tool_call takes session_id directly, client name/version via properties): sess = get_mcp_session(request) # None until the client replays the token posthog.capture_tool_call( name, session_id=sess.session_id if sess else None, - client_name=sess.client_name if sess else None, - client_version=sess.client_version if sess else None, intent=prepared.intent, + properties={ + "$mcp_client_name": sess.client_name if sess else None, + "$mcp_client_version": sess.client_version if sess else None, + }, ) ``` From 19e9511fe2e7c0321efa07233d0c80d05d44388f Mon Sep 17 00:00:00 2001 From: Georgis Andonis Date: Mon, 27 Jul 2026 13:40:37 +0300 Subject: [PATCH 04/10] docs(mcp-analytics): clarify where stateless tokens are zero-config vs manual The auto-wire is FastMCP-family-specific (it wraps app factories only FastMCP exposes). Reword the Python note: zero-config on FastMCP (official + jlowin 2.0) via stateless_http; add the middleware yourself for a low-level Server or a custom PostHogMCP dispatcher, where you build the ASGI app. Generated-By: PostHog Code Task-Id: f44ec5e0-b836-4d13-98c4-c26887dd7ec2 --- contents/docs/mcp-analytics/installation.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contents/docs/mcp-analytics/installation.mdx b/contents/docs/mcp-analytics/installation.mdx index 3a53b3c45646..f101f8789696 100644 --- a/contents/docs/mcp-analytics/installation.mdx +++ b/contents/docs/mcp-analytics/installation.mdx @@ -219,7 +219,7 @@ if (body?.method === "initialize" && !req.headers[MCP_SESSION_HEADER]) { Some frameworks construct the transport for you and don't expose `enableJsonResponse`, and a client that ignores the header falls back to a generated session per request either way. In both cases, group by user with [`identify`](/docs/mcp-analytics/identifying-users) — `distinct_id` groups a person's calls however many requests they span, and it requires nothing from the client. [Conversation IDs](/docs/mcp-analytics/conversation-id) give finer, per-conversation grouping when the agent cooperates. -In **Python**, `instrument()` handles this automatically on a stateless `FastMCP(stateless_http=True)` server, with no `enableJsonResponse` equivalent to set (it mints from an ASGI layer, so JSON and SSE both work). A custom `PostHogMCP` dispatcher adds [`PostHogMcpStatelessSessionMiddleware`](/docs/mcp-analytics/custom-servers). +In **Python**, session tokens work with any HTTP MCP server, and there's no `enableJsonResponse` caveat — the token is minted from an ASGI layer, so JSON and SSE both work. On a **FastMCP** server (official `mcp.server.fastmcp` or jlowin's `fastmcp` 2.0) it's zero-config: `instrument()` wraps the server's `streamable_http_app()` / `sse_app()` factories, so just set `stateless_http=True`. When you build the ASGI app yourself — a low-level `Server`, or a custom [`PostHogMCP`](/docs/mcp-analytics/custom-servers) dispatcher — add `PostHogMcpStatelessSessionMiddleware` to that app once. ## Python From 832860e928ba8cad8d06b566a748e89fb489c368 Mon Sep 17 00:00:00 2001 From: Georgis Andonis Date: Mon, 27 Jul 2026 13:49:03 +0300 Subject: [PATCH 05/10] docs(mcp-analytics): give Python its own stateless subsection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Python note previously sat under "### When you can't use a session token" — contradicting that heading, since Python session tokens DO work. Move it to its own "### Python" subsection alongside the TypeScript-specific ones. Generated-By: PostHog Code Task-Id: f44ec5e0-b836-4d13-98c4-c26887dd7ec2 --- contents/docs/mcp-analytics/installation.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contents/docs/mcp-analytics/installation.mdx b/contents/docs/mcp-analytics/installation.mdx index f101f8789696..4603fe12bd79 100644 --- a/contents/docs/mcp-analytics/installation.mdx +++ b/contents/docs/mcp-analytics/installation.mdx @@ -219,7 +219,9 @@ if (body?.method === "initialize" && !req.headers[MCP_SESSION_HEADER]) { Some frameworks construct the transport for you and don't expose `enableJsonResponse`, and a client that ignores the header falls back to a generated session per request either way. In both cases, group by user with [`identify`](/docs/mcp-analytics/identifying-users) — `distinct_id` groups a person's calls however many requests they span, and it requires nothing from the client. [Conversation IDs](/docs/mcp-analytics/conversation-id) give finer, per-conversation grouping when the agent cooperates. -In **Python**, session tokens work with any HTTP MCP server, and there's no `enableJsonResponse` caveat — the token is minted from an ASGI layer, so JSON and SSE both work. On a **FastMCP** server (official `mcp.server.fastmcp` or jlowin's `fastmcp` 2.0) it's zero-config: `instrument()` wraps the server's `streamable_http_app()` / `sse_app()` factories, so just set `stateless_http=True`. When you build the ASGI app yourself — a low-level `Server`, or a custom [`PostHogMCP`](/docs/mcp-analytics/custom-servers) dispatcher — add `PostHogMcpStatelessSessionMiddleware` to that app once. +### Python + +The subsections above are TypeScript-specific. In Python session tokens work with any HTTP MCP server, and there's no `enableJsonResponse` caveat — the token is minted from an ASGI layer, so JSON and SSE both work. On a **FastMCP** server (official `mcp.server.fastmcp` or jlowin's `fastmcp` 2.0) it's zero-config: `instrument()` wraps the server's `streamable_http_app()` / `sse_app()` factories, so just set `stateless_http=True`. When you build the ASGI app yourself — a low-level `Server`, or a custom [`PostHogMCP`](/docs/mcp-analytics/custom-servers) dispatcher — add `PostHogMcpStatelessSessionMiddleware` to that app once. ## Python From 4dea65296b9f5eb40bed24bccd50aae67a0c7f09 Mon Sep 17 00:00:00 2001 From: Georgis Andonis Date: Mon, 27 Jul 2026 13:53:13 +0300 Subject: [PATCH 06/10] docs(mcp-analytics): consolidate stateless Python into the Python section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid two Python sections. Drop the `### Python` note under the (TypeScript) stateless section and add a self-contained `### Stateless and multi-pod servers` subsection inside `## Python` — with runnable FastMCP + middleware snippets. The TS section now points Python readers down; both languages are discoverable. Generated-By: PostHog Code Task-Id: f44ec5e0-b836-4d13-98c4-c26887dd7ec2 --- contents/docs/mcp-analytics/installation.mdx | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/contents/docs/mcp-analytics/installation.mdx b/contents/docs/mcp-analytics/installation.mdx index 4603fe12bd79..02196fcfb1c5 100644 --- a/contents/docs/mcp-analytics/installation.mdx +++ b/contents/docs/mcp-analytics/installation.mdx @@ -182,6 +182,8 @@ A stateless server keeps nothing between requests — a fresh server instance ea The SDK handles this with no session store and no sticky routing. At `initialize` it mints the `Mcp-Session-Id` response header as a token carrying the session id and client metadata. Clients replay that header on every subsequent request, so any pod reads the same values back — nothing changes on the client side. +The rest of this section covers the TypeScript SDK. **Using Python?** See [Python](#python) below — stateless works out of the box on FastMCP, with no `enableJsonResponse` caveat. + ### Streamable HTTP needs `enableJsonResponse: true` Minting only reaches the wire in JSON mode. In SSE (streaming) mode, `StreamableHTTPServerTransport` builds the response headers *before* your `initialize` handler runs, so the minted header never lands and the SDK silently falls back to a session per request: @@ -219,10 +221,6 @@ if (body?.method === "initialize" && !req.headers[MCP_SESSION_HEADER]) { Some frameworks construct the transport for you and don't expose `enableJsonResponse`, and a client that ignores the header falls back to a generated session per request either way. In both cases, group by user with [`identify`](/docs/mcp-analytics/identifying-users) — `distinct_id` groups a person's calls however many requests they span, and it requires nothing from the client. [Conversation IDs](/docs/mcp-analytics/conversation-id) give finer, per-conversation grouping when the agent cooperates. -### Python - -The subsections above are TypeScript-specific. In Python session tokens work with any HTTP MCP server, and there's no `enableJsonResponse` caveat — the token is minted from an ASGI layer, so JSON and SSE both work. On a **FastMCP** server (official `mcp.server.fastmcp` or jlowin's `fastmcp` 2.0) it's zero-config: `instrument()` wraps the server's `streamable_http_app()` / `sse_app()` factories, so just set `stateless_http=True`. When you build the ASGI app yourself — a low-level `Server`, or a custom [`PostHogMCP`](/docs/mcp-analytics/custom-servers) dispatcher — add `PostHogMcpStatelessSessionMiddleware` to that app once. - ## Python A Python SDK ships inside the [`posthog`](/docs/libraries/python) package (the same way [`posthog.ai`](/docs/ai-engineering) does), so there's nothing extra to install: @@ -284,6 +282,26 @@ instrument(server, posthog, MCPAnalyticsOptions( | `event_properties` | `(request, extra) -> dict` | — | Properties merged onto every event. | | `logger` | `(message: str) -> None` | no-op | STDIO-safe log sink. | +### Stateless and multi-pod servers + +Same problem as [above](#stateless-and-multi-pod-servers) — a stateless deployment fragments `$session_id` and loses the client name/version after `initialize`. Python fixes it with the same self-encoded session token (minted onto the `Mcp-Session-Id` header, replayed by the client), but from an ASGI layer, so there's **no `enableJsonResponse` caveat** — JSON and SSE both work. + +On a **FastMCP** server (official `mcp.server.fastmcp` or jlowin's `fastmcp` 2.0) it's zero-config: `instrument()` wraps the server's `streamable_http_app()` / `sse_app()` factories (which `run()` uses too), so just make the server stateless: + +```python +server = FastMCP("my-server", stateless_http=True) +instrument(server, posthog) +server.run(transport="streamable-http") # or: app = server.streamable_http_app() +``` + +When you build the ASGI app yourself — a low-level `Server`, or a custom [`PostHogMCP`](/docs/mcp-analytics/custom-servers) dispatcher — add the middleware to that app once: + +```python +from posthog.mcp import PostHogMcpStatelessSessionMiddleware + +app.add_middleware(PostHogMcpStatelessSessionMiddleware) +``` + ### Flushing on exit The `posthog` client batches events asynchronously and you own its lifecycle. On the `instrument()` path, auto-captured events are scheduled in the background — `await analytics.flush()` waits for in-flight events, then `posthog.flush()` / `posthog.shutdown()` sends them. Call this from your shutdown/`SIGTERM` handler so trailing events aren't dropped (see [`examples/mcp_analytics_demo.py`](https://github.com/PostHog/posthog-python/blob/main/examples/mcp_analytics_demo.py) for a runnable end-to-end example): From 3c6b4221fd97f1ede0422d8c8a042fe1062690bb Mon Sep 17 00:00:00 2001 From: Georgis Andonis Date: Mon, 27 Jul 2026 13:55:43 +0300 Subject: [PATCH 07/10] docs(mcp-analytics): drop redundant Python pointer in stateless section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dedicated ## Python section now has its own stateless subsection, and no other TypeScript section carries a "Python? see below" pointer — so the line was redundant and inconsistent. Removed. Generated-By: PostHog Code Task-Id: f44ec5e0-b836-4d13-98c4-c26887dd7ec2 --- contents/docs/mcp-analytics/installation.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/contents/docs/mcp-analytics/installation.mdx b/contents/docs/mcp-analytics/installation.mdx index 02196fcfb1c5..c224165b1b06 100644 --- a/contents/docs/mcp-analytics/installation.mdx +++ b/contents/docs/mcp-analytics/installation.mdx @@ -182,8 +182,6 @@ A stateless server keeps nothing between requests — a fresh server instance ea The SDK handles this with no session store and no sticky routing. At `initialize` it mints the `Mcp-Session-Id` response header as a token carrying the session id and client metadata. Clients replay that header on every subsequent request, so any pod reads the same values back — nothing changes on the client side. -The rest of this section covers the TypeScript SDK. **Using Python?** See [Python](#python) below — stateless works out of the box on FastMCP, with no `enableJsonResponse` caveat. - ### Streamable HTTP needs `enableJsonResponse: true` Minting only reaches the wire in JSON mode. In SSE (streaming) mode, `StreamableHTTPServerTransport` builds the response headers *before* your `initialize` handler runs, so the minted header never lands and the SDK silently falls back to a session per request: From c6286f2685f42d25fb42cf65028d9324202a55f9 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:35:42 +0000 Subject: [PATCH 08/10] chore(visual): update playwright baselines 2 updated Run: 1e15d7e5-bdd9-40e8-998d-30b6a71cf1f7 Co-authored-by: gesh <6430745+gesh@users.noreply.github.com> --- .snapshots.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.snapshots.yml b/.snapshots.yml index 7fafc26ab0ef..14e9d6dec328 100644 --- a/.snapshots.yml +++ b/.snapshots.yml @@ -31,7 +31,7 @@ snapshots: pricing--desktop: hash: v1.k54649d6d.86c2b292ff02e47ad5108af5ba373f2ca35dcfa21f348eb9ea1b42e359738b62.A6B3pTKcyehwxJvuKPSGckDNlu03eMegU-3RiSowieI pricing--mobile: - hash: v1.k54649d6d.0960f404d5bac7e7e64fe38f7ff21cadecc89d960074be9725c5fc8dfcb80d1b.GlhFE8hjyRL2xppjOhGeqZ5KhJU3-QPyD0W94lsdlOU + hash: v1.k54649d6d.24e92b64e1b0bac92a56986b13fe26d9a70aa82cc1bf2d9ae5762bf24ee6d334.1TPHqr822I1rmLQf8hEGTFaMA9wQYDq4BVMjTKjpmZs product-analytics--desktop: hash: v1.k54649d6d.93209c9c499bc9651ad6991098a9c7d0deeed55f40b3f72f7c7a835e3177627a.9723yEHo8tmRt44URKU_IitJC9NrSqAVBV9HFb2W2H0 product-analytics--mobile: @@ -43,4 +43,4 @@ snapshots: surveys--desktop: hash: v1.k54649d6d.fff1c7652fe7cfc4de61ea6fd8c9821255cefbcfc9d956423f525bcc8a633cc4.SQoHuWZAI_t0dALwUc-xYOqI8CfCuAAjB4REZWt2Rjs surveys--mobile: - hash: v1.k54649d6d.fa499a502301a1d93c877a9ac62ca9df2d64fd405e1db502ba1b543b1674b57d.Tz1MqBKasGzORj_prX6kW9JKCdf619Y8Ta2fPiK82nM + hash: v1.k54649d6d.3bfb92842973342add36bb85fda355a8cd5b718d5df557e7de7dfe6f63c8ccd2.MzHKvwQpkAT-tT2DknX-xnOwltCkNfW_QbWCd3UVsic From 10da0c0cca4385bccaed1613300550040d9f269e Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:51:35 +0000 Subject: [PATCH 09/10] chore(visual): update playwright baselines 1 updated Run: 78ce28b8-34bd-4163-ad1e-0a42fd40a02b Co-authored-by: gesh <6430745+gesh@users.noreply.github.com> --- .snapshots.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.snapshots.yml b/.snapshots.yml index 14e9d6dec328..536560c8488f 100644 --- a/.snapshots.yml +++ b/.snapshots.yml @@ -31,7 +31,7 @@ snapshots: pricing--desktop: hash: v1.k54649d6d.86c2b292ff02e47ad5108af5ba373f2ca35dcfa21f348eb9ea1b42e359738b62.A6B3pTKcyehwxJvuKPSGckDNlu03eMegU-3RiSowieI pricing--mobile: - hash: v1.k54649d6d.24e92b64e1b0bac92a56986b13fe26d9a70aa82cc1bf2d9ae5762bf24ee6d334.1TPHqr822I1rmLQf8hEGTFaMA9wQYDq4BVMjTKjpmZs + hash: v1.k54649d6d.50e343a2027fa80953b73debc3b40a6d7a6e2728601295ebc20babb63a457fd0.ufQXfW6xh-1Ac8FmZxCr5JkT0kn_6zzNRAPoZDm93Xg product-analytics--desktop: hash: v1.k54649d6d.93209c9c499bc9651ad6991098a9c7d0deeed55f40b3f72f7c7a835e3177627a.9723yEHo8tmRt44URKU_IitJC9NrSqAVBV9HFb2W2H0 product-analytics--mobile: From 24d00f7972c3a2142cb57552ba28f8e19b6d98c5 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:07:05 +0000 Subject: [PATCH 10/10] chore(visual): update playwright baselines 1 updated Run: 9e5b7454-d2c0-4edd-8311-f561104bc799 Co-authored-by: gesh <6430745+gesh@users.noreply.github.com> --- .snapshots.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.snapshots.yml b/.snapshots.yml index 536560c8488f..d7bbdc71ad08 100644 --- a/.snapshots.yml +++ b/.snapshots.yml @@ -31,7 +31,7 @@ snapshots: pricing--desktop: hash: v1.k54649d6d.86c2b292ff02e47ad5108af5ba373f2ca35dcfa21f348eb9ea1b42e359738b62.A6B3pTKcyehwxJvuKPSGckDNlu03eMegU-3RiSowieI pricing--mobile: - hash: v1.k54649d6d.50e343a2027fa80953b73debc3b40a6d7a6e2728601295ebc20babb63a457fd0.ufQXfW6xh-1Ac8FmZxCr5JkT0kn_6zzNRAPoZDm93Xg + hash: v1.k54649d6d.c4a01959d853183f465782d56ce1d0a6bd6f1385f1cd03349430f937c0dc1e59.oJN4qafmXzE03Fl0tudiFZKdSAvyHQHy3TG_tP02q2U product-analytics--desktop: hash: v1.k54649d6d.93209c9c499bc9651ad6991098a9c7d0deeed55f40b3f72f7c7a835e3177627a.9723yEHo8tmRt44URKU_IitJC9NrSqAVBV9HFb2W2H0 product-analytics--mobile: