Skip to content

Integrating Mitr Analytics β€” a guide for AI coding assistants

This page is written to be handed directly to an AI assistant (Claude, ChatGPT, Copilot, Cursor, etc.) β€” pasted into a chat, or fetched by an agent from /docs/ai-integration.md (plain markdown, same content). Following it end-to-end, an assistant should be able to fully wire up Mitr Analytics in a client's codebase without back-and-forth beyond the two questions in Step 1.

If you are that assistant: work through the steps in order. Steps 1–2 need information only the human has. Steps 3 onward you can do yourself by reading the project you're working in.

Step 1 β€” Get the two things you need from the human

Ask (only if not already provided):

  1. Site ID β€” a UUID from the Mitr dashboard (Integration Hub β†’ their app's card, or the "SDK Setup" step of the "Add New App" wizard).
  2. Platform β€” a website (plain HTML or a JS framework), or an app (Flutter, React Native, native iOS/macOS in Swift, native Android in Kotlin), or a backend/server (Node.js)? Every platform except plain web/no-code also needs the Secret Key from the same dashboard screen β€” non-web platforms have no browser Origin header to check, so the secret key authenticates every request instead.

Web snippets load the SDK from https://mitranalytics.dev/vendor/mitr.js, the canonical URL. Events go to https://api.mitranalytics.dev/api/v1, the SDK's built-in default β€” set data-base-url only to point at a self-hosted or staging backend. You can also self-host mitr.js from your own static assets; the API is identical either way.

If the human doesn't have a Site ID yet, tell them: "Go to your Mitr dashboard β†’ Integration Hub β†’ Add New App, choose your platform, and enter your domain (for a website) or bundle/package ID (for an app). You'll get a Site ID immediately, plus a Secret Key if you chose a non-web platform." You cannot generate a Site ID yourself.

Do not invent a placeholder Site ID and ship it. Use a literal placeholder like YOUR_SITE_ID until the human gives you a real one.

Step 2 β€” Detect the platform (if not already told)

You find...PlatformGo to
pubspec.yaml with flutter: sectionFlutter appStep 3C
package.json with "next"Next.jsStep 3B
package.json with "react" (no Next, no "react-native")React SPA (web)Step 3B
package.json with "react-native", or an ios/android folder from RN/ExpoReact Native appStep 3D
package.json with "vue" / "nuxt"Vue / NuxtStep 3B
Package.swift or an Xcode project, no Flutter/RNNative iOS/macOS (Swift)Step 3E
build.gradle(.kts) targeting Android, no Flutter/RNNative Android (Kotlin)Step 3F
package.json with no browser entry point β€” an API route, cron script, or CLINode.js backendStep 3G
Email template, restrictive CSP, or an HTML-only no-code fieldNo-JS environmentStep 3H
Plain .html files, no frameworkStatic siteStep 3A
WordPress/Shopify/Webflow/Squarespace/Wix (human describes it)No-code platformStep 3A (no-code variant)
No JS repo at allAsk which of the above, or default to Step 3A no-code variantβ€”

None of the mobile/desktop/server SDKs (Flutter, React Native, Swift, Kotlin, Node) are on a public package registry yet (pub.dev, npm, Maven Central, SPM index) β€” they ship from mitr-sdk-suite as local/git/path dependencies. If the human's repo has no vendored copy, tell them it needs to be added first.

Step 3A β€” Plain HTML / static site / no-code platform

Paste this once, right before </head> or the closing </body> tag:

<script src="https://mitranalytics.dev/vendor/mitr.js"
        data-site-id="YOUR_SITE_ID"
        async defer></script>

That's it β€” no other code required. It auto-tracks page views, including client-side route changes if the site later adds one.

No-code platforms (WordPress, Shopify, Webflow, Squarespace, Wix): the human pastes the exact same snippet into their platform's "Custom Code" / "Header Scripts" / "Tracking Code" section β€” every one of these platforms has such a setting.

Do not add a secret key anywhere in web output β€” it isn't needed and shouldn't be embedded in public HTML/JS.

Step 3B β€” JS framework (React, Next.js, Vue, Nuxt, etc.)

Add the SDK once near your app's root/entry point β€” not to every page/component.

Plain script tag (simplest, works anywhere):

<script src="https://mitranalytics.dev/vendor/mitr.js" data-site-id="YOUR_SITE_ID" async defer></script>

Next.js (App Router) β€” app/layout.jsx:

'use client';
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html><body>
      {children}
      <Script src="https://mitranalytics.dev/vendor/mitr.js" data-site-id="YOUR_SITE_ID" strategy="afterInteractive" />
    </body></html>
  );
}

React SPA β€” call once at startup instead of a script tag, if preferred:

useEffect(() => { window.Mitr.init('YOUR_SITE_ID'); }, []);

Auto-tracking already covers client-side route changes β€” no per-route tracking calls needed for React Router, Vue Router, or similar.

Custom events, wherever the relevant action happens:

window.Mitr.track('signup_completed', { plan: 'pro' });

On login/logout, if the app has auth:

window.Mitr.identify(user.id); // after login
window.Mitr.reset();           // on logout

Full API reference: JavaScript SDK.

Step 3C β€” Flutter app (iOS / Android / desktop / Flutter Web)

  1. Add the dependency (adjust the path to wherever this SDK lives relative to the client's project β€” tell the human it needs to be vendored locally, since it isn't on pub.dev yet):
    dependencies:
      mitr_flutter:
        path: ../path/to/mitr-sdk-suite/mitr_flutter
    Then run flutter pub get.
  2. Initialize once, at the top of main(), using both the Site ID and Secret Key from Step 1:
    import 'package:mitr_flutter/mitr_flutter.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await MitrAnalytics.instance.initialize(
        siteId: 'YOUR_SITE_ID',
        secretKey: 'YOUR_SECRET_KEY',
      );
      runApp(const MyApp());
    }
  3. Add automatic screen tracking to the app's router:
    MaterialApp(
      navigatorObservers: [MitrNavigationObserver()],
      // ...
    )
    (Use observers: instead if the app uses GoRouter.)
  4. Custom events, wherever relevant:
    await MitrAnalytics.instance.track('purchase_completed', meta: {'value': 49.99});
  5. On login/logout, if the app has auth:
    MitrAnalytics.instance.identify(user.id); // after login
    MitrAnalytics.instance.reset();           // on logout

Full API reference: Flutter SDK.

Step 3D β€” React Native (bare RN or Expo dev-client)

  1. Add the dependency and its one required peer (not yet published to npm β€” if there's no vendored copy in the repo, tell the human it needs to be added from mitr-sdk-suite/mitr-react-native first). Needs a dev client, not Expo Go β€” it uses AsyncStorage, unavailable in Expo Go's managed sandbox:
    npm install @mitr/react-native @react-native-async-storage/async-storage
  2. Initialize once near the app root, using both the Site ID and Secret Key:
    import { MitrAnalytics } from '@mitr/react-native';
    
    export const mitr = new MitrAnalytics({
      siteId: 'YOUR_SITE_ID',
      secretKey: 'YOUR_SECRET_KEY',
    });
  3. Automatic screen tracking, if the app uses React Navigation:
    <NavigationContainer onStateChange={(state) => mitr.trackNavigationStateChange(state)}>
    Otherwise call mitr.pageView('ScreenName') manually wherever screens change.
  4. Custom events and identify/reset β€” same shape as the web SDK:
    await mitr.track('signup_completed', { metadata: { plan: 'pro' } });
    mitr.identify(user.id); // after login
    mitr.reset();           // on logout

Full API reference: React Native SDK.

Step 3E β€” Native iOS/macOS (Swift)

  1. Add as a Swift Package dependency (Xcode: File β†’ Add Package Dependencies, pointing at mitr-sdk-suite/mitr-swift β€” not yet published as a versioned/hosted package).
  2. Initialize once, using both the Site ID and Secret Key. Every call is async β€” MitrAnalytics is an actor:
    import MitrAnalytics
    
    let mitr = MitrAnalytics(options: .init(
        siteId: "YOUR_SITE_ID",
        secretKey: "YOUR_SECRET_KEY"
    ))
  3. Custom events, screen views, identify/reset:
    await mitr.track("purchase_completed", metadata: ["value": 49.99])
    await mitr.pageView("HomeScreen")
    await mitr.identify(user.id) // after login
    await mitr.reset()           // on logout
  4. Deep-link attribution needs a manual hook (Swift has no SDK-level lifecycle hook, unlike React Native):
    .onOpenURL { url in Task { await mitr.captureDeepLink(url) } }

Full API reference: Swift SDK.

Step 3F β€” Native Android (Kotlin)

  1. Add as a local Gradle module dependency (not yet published to Maven Central):
    // app/build.gradle.kts
    dependencies {
        implementation(project(":mitr-kotlin:android"))
    }
  2. Initialize once, using both the Site ID and Secret Key. Every method is suspend β€” call from a coroutine:
    import dev.mitranalytics.sdk.android.MitrAnalytics
    import dev.mitranalytics.sdk.core.MitrAnalyticsOptions
    
    val mitr = MitrAnalytics(
        context = applicationContext,
        options = MitrAnalyticsOptions(siteId = "YOUR_SITE_ID", secretKey = "YOUR_SECRET_KEY"),
    )
  3. Custom events, screen views, identify/reset:
    lifecycleScope.launch {
        mitr.track("purchase_completed", metadata = mapOf("value" to 49.99))
        mitr.pageView("HomeScreen")
        mitr.identify(user.id) // after login
    }

Full API reference: Kotlin SDK.

Step 3G β€” Node.js backend (server, webhook handler, cron, CLI)

  1. Install (not yet published to npm β€” if no vendored copy exists, add it from mitr-sdk-suite/mitr-node first):
    npm install @mitr/node
  2. Initialize once, using both the Site ID and Secret Key:
    import { MitrAnalytics } from '@mitr/node';
    
    const mitr = new MitrAnalytics({
      siteId: 'YOUR_SITE_ID',
      secretKey: 'YOUR_SECRET_KEY',
    });
  3. Multi-user servers: pass userId per call rather than calling identify() β€” a server process serves many end users, unlike a browser tab or app instance:
    await mitr.track('checkout_completed', {
      userId: req.user.id,
      metadata: { amount: req.body.amount },
    });
  4. Always flush before exit β€” nothing is persisted to disk on this SDK, unlike the browser/mobile ones:
    process.on('SIGTERM', async () => {
      await mitr.close();
      process.exit(0);
    });

Full API reference: Node.js SDK.

Step 3H β€” No-JS environment (email, restrictive CSP, HTML-only no-code field)

There's no SDK to install β€” the whole mechanism is one <img> tag:

<img src="https://api.mitranalytics.dev/api/v1/p.gif?sid=YOUR_SITE_ID&p=/newsletter&t=email_open"
     width="1" height="1" alt="" style="display:none" />

sid is required; p (path), t (event type), uid (pre-hashed user id), utm_*, and meta_* (custom properties) are optional query params. No batching, retry, or identify()/reset() session state β€” a pixel fires once per load and always returns a valid image even if the underlying event was rejected, so don't rely on it for delivery confirmation; check the dashboard instead.

Full reference: Tracking Pixel.

Step 4 β€” Verify it worked

  1. Run the site/app and navigate around.
  2. In the Mitr dashboard β†’ Integration Hub, open the app's card and click "Check connection" β€” it should flip from "Pending" to "Connected" within a few seconds of the first event being sent.
  3. If it doesn't:
    • Web: open devtools β†’ Network tab, find the request to .../api/v1/event. A 403 means the page's domain doesn't match what was registered (common cause: testing on a different domain/subdomain β€” localhost is always allowed in non-production environments). A 404 means the Site ID is wrong or the app was deleted.
    • Flutter/React Native/Swift/Kotlin (debug builds): check the console β€” initialization fires a diagnostic ping and logs the result immediately, most commonly flagging a wrong Secret Key.
    • Node.js: pass debug: true in the constructor options to log each batch send attempt and its result.

Things to never do

  • Never put a mobile/desktop/server Secret Key into web-facing HTML/JS β€” plain web doesn't need one and it would be publicly visible in page source.
  • Never pass a raw email, name, or other PII into track/identify/pageView metadata β€” only pass an opaque user id to identify; every SDK hashes it before it's sent (client-side for Flutter/RN/Swift/Kotlin/web, server-side inside the process for Node).
  • Never call init/initialize/new MitrAnalytics(...) more than once per app instance, and never block app/request startup waiting on it β€” every SDK degrades gracefully (queue locally, retry later) if the network or backend is temporarily unavailable. The one exception: @mitr/node queues in memory only, so always call await mitr.close() in the shutdown handler or the final batch is lost.
  • Never assume a mobile/desktop/server SDK is on a public package registry β€” none of Flutter, React Native, Swift, Kotlin, or Node.js are published yet. They come from mitr-sdk-suite as local/git/path dependencies.

Have questions about integration or security? Drop us a line directly.