iOS, Android and React Native

There is no mobile SDK to install. An app records events by posting the same JSON the web tracker posts, to the same endpoint, with one extra field that says it is an app.

POST https://app.stepmetrics.co/v1/collect
content-type: application/json

{
  "siteId": "YOUR_SITE_ID",
  "name":   "pageview",
  "path":   "/checkout",
  "client": { "platform": "ios", "device": "mobile" },
  "props":  { "appVersion": "2.1.0" }
}

Why client is not optional for an app

Send it. Without it your traffic is filed as bot traffic and disappears from every default view, and nothing will tell you that is happening.

The reason is that bot filtering reads the user agent, and that test assumes a browser wrote it. An empty user agent is a bot, because a real browser always sends one. A user agent containing axios/ is a bot, because on the web it is. Both of those describe ordinary mobile HTTP stacks: URLSession, OkHttp and React Native's fetch may send nothing at all, and an app built on axios announces itself as axios. Declaring client tells the server to judge you as an app instead.

It is a declaration, not an exemption. If you also send automation: true you are still recorded as automated, and the pace-based check that runs after the fact still applies.

The fields an app sends

FieldRequiredNotes
siteIdyes Same id as the web tag. One site can take web and app traffic together.
pathyes The screen, as a stable path: /checkout, /settings/billing. Use the route, never a display title, or the same screen splits across languages.
client.platformyes, for apps ios or android. In React Native this is Platform.OS exactly. It is recorded as the OS, so app traffic segments beside web traffic instead of forming a second vocabulary. A backend reporting on a person's behalf sends server - see From a server.
client.deviceno mobile or tablet. Omit it and the device shows as Unknown. Nothing is guessed on your behalf.
nameno Defaults to pageview. Custom events work exactly as on the web.
propsno Where app version, build number and framework belong. There are no dedicated fields for them, because a field the server accepted and then dropped would be worse than no field.
revenueCents, currencyno Minor units, so in-app purchase totals stay integral.

React Native

import { Platform } from 'react-native';

export function track(path, name = 'pageview', props) {
  return fetch('https://app.stepmetrics.co/v1/collect', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
  siteId: YOUR_SITE_ID,
  name,
  path,
  client: { platform: Platform.OS, device: Platform.isPad ? 'tablet' : 'mobile' },
  props,
}),
  });
}

Swift

func track(path: String, name: String = "pageview") {
var req = URLRequest(url: URL(string: "https://app.stepmetrics.co/v1/collect")!)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try? JSONSerialization.data(withJSONObject: [
    "siteId": yourSiteId,
    "name": name,
    "path": path,
    "client": ["platform": "ios",
               "device": UIDevice.current.userInterfaceIdiom == .pad ? "tablet" : "mobile"],
])
URLSession.shared.dataTask(with: req).resume()
}

Kotlin

fun track(path: String, name: String = "pageview") {
val body = JSONObject(mapOf(
    "siteId" to yourSiteId,
    "name" to name,
    "path" to path,
    "client" to JSONObject(mapOf("platform" to "android", "device" to "mobile")),
)).toString().toRequestBody("application/json".toMediaType())

client.newCall(Request.Builder().url("https://app.stepmetrics.co/v1/collect").post(body).build())
      .enqueue(/* fire and forget */)
}

What is different from the web

  • No API key. The endpoint is write-only and the site id is public, exactly as in the browser. Never ship a read key in an app; anyone can extract it.
  • Visitors are counted per IP and HTTP client per day. A phone moving from cellular to wifi counts as a new visitor, and several devices behind one NAT with an identical client can collapse into one. That follows from storing no durable per-person id, which is the same trade the web side makes.
  • No automatic screen tracking. Call it from your navigation listener. The web tracker hooks history.pushState; an app has no equivalent to hook.
  • Browser shows as Unknown. An app is not a browser, and inventing a name for it would put a guess where a fact belongs.