Ingest live data

Last updated:

PostHog enables you to analyze data in real-time, as events come in. Make full use of this power by ingesting live data with our analytics integrations: client libraries, server libraries, as well as third-party platforms.

The purpose of this guide is to help you understand some key concepts with a goal of ingesting live data into PostHog. For simplicity, we'll focus on client libraries as a means of data ingestion.

The guide covers the following:

If you prefer to learn by doing, you can get started on the web with the JavaScript snippet.

Note that some events with a never-before-seen distinct ID are deliberately delayed by around a minute. More on that in the "Event ingestion nuances" section.

Install a library

Install the library for the platform you are building your application for.

You can either load the snippet as a script in your HTML:

JavaScript snippet Recommended

This is the simplest way to get PostHog up and running on your website, and only takes a few minutes to set-up.

Add to your website & app

Paste this snippet within the <head> tags of your website - ideally just inside the closing </head> tag - on all pages that you wish to track.

HTML
<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
posthog.init('<ph_project_api_key>', {api_host: '<ph_instance_address>'})
</script>

Be sure to replace <ph_project_api_key> and <ph_instance_address> with your project's values. (You can find the snippet pre-filled with this data in the PostHog app under Project / Settings. (Quick links if you use PostHog Cloud US or PostHog Cloud EU)

What this code does

After adding the snippet to your website, it will automatically start to:

Tips

  • Add the snippet on your (marketing) website and your application using the same PostHog project. That means you'll be able to follow a user from the moment they come onto your website, all the way through sign up and their actual usage of your product.
  • Track users across multiple domains by including the same project snippet on all sites you wish to track.
  • Disable tracking sensitive information by adding the ph-no-capture class to elements you don't want to track. (We do this automatically for common fields like passwords and credit cards.)

Next steps

  • Add permitted domains in your PostHog project settings so you can create actions using the PostHog toolbar directly on your site - a convenient low-code option.
  • Send custom events from your codebase for things like signups, purchases, and more KPIs.

The dedicated JavaScript library page has more details about everything you can do with posthog-js.

Place the snippet in the <head> tags of your website, ideally just above the closing </head> tag. You will need to do this for all pages that you wish to track.

Or you can include it using npm, by doing either:

yarn add posthog-js

And then include it in your files:

JavaScript
import posthog from 'posthog-js'
posthog.init('<ph_project_api_key>', { api_host: '<ph_instance_address>' })

If you don't want to send a bunch of test data while you're developing, you could do the following:

JavaScript
if (!window.location.host.includes('127.0.0.1') && !window.location.host.includes('localhost')) {
posthog.init('<ph_project_api_key>', { api_host: '<ph_instance_address>' })
}

If you're using React or Next.js, checkout our React SDK or Next.js integration.

The best way to install the PostHog Android library is with a build system like Gradle. This ensures you can easily upgrade to the latest versions.

All you need to do is add the posthog module to your build.gradle:

Terminal
dependencies {
implementation 'com.posthog.android:posthog:1.+'
}

The best place to initialize the client is in your Application subclass.

Java
public class SampleApp extends Application {
private static final String POSTHOG_API_KEY = "<ph_project_api_key>";
private static final String POSTHOG_HOST = "<ph_instance_address>";
@Override
public void onCreate() {
// Create a PostHog client with the given context, API key and host.
PostHog posthog = new PostHog.Builder(this, POSTHOG_API_KEY, POSTHOG_HOST)
.captureApplicationLifecycleEvents() // Record certain application events automatically!
.recordScreenViews() // Record screen views automatically!
.build();
// Set the initialized instance as a globally accessible instance.
PostHog.setSingletonInstance(posthog);
// Now anytime you call PostHog.with, the custom instance will be returned.
PostHog posthog = PostHog.with(this);
}
}

PostHog is available through CocoaPods and Carthage or you can add it as a Swift Package Manager based dependency.

CocoaPods

Podfile
pod "PostHog", "~> 1.1"

Carthage

Cartfile
github "posthog/posthog-ios"

Swift Package Manager

Add PostHog as a dependency in your Xcode project "Package Dependencies" and select the project target for your app, as appropriate.

For a Swift Package Manager based project, add PostHog as a dependency in your "Package.swift" file's Package dependencies section:

Package.swift
dependencies: [
.package(url: "https://github.com/PostHog/posthog-ios.git", from: "2.0.0")
],

and then as a dependency for the Package target utilizing PostHog:

Swift
.target(
name: "myApp",
dependencies: [.product(name: "PostHog", package: "posthog-ios")]),

With Objective-C

Objective-C
#import <PostHog/PHGPostHog.h>
#import <PostHog/PHGPostHogConfiguration.h>
// on posthog.com
PHGPostHogConfiguration *configuration = [PHGPostHogConfiguration configurationWithApiKey:@"YOUR_API_KEY"];
// self-hosted
PHGPostHogConfiguration *configuration = [PHGPostHogConfiguration configurationWithApiKey:@"YOUR_API_KEY"
host:@"https://app.posthog.com"];
configuration.captureApplicationLifecycleEvents = YES; // Record certain application events automatically!
configuration.recordScreenViews = YES; // Record screen views automatically!
[PHGPostHog setupWithConfiguration:configuration];

With Swift

Swift
import PostHog
// `host` is optional if you use PostHog Cloud (app.posthog.com)
let configuration = PHGPostHogConfiguration(apiKey: "<ph_project_api_key>", host: "<ph_instance_address>")
configuration.captureApplicationLifecycleEvents = true; // Record certain application events automatically!
configuration.recordScreenViews = true; // Record screen views automatically!
PHGPostHog.setup(with: configuration)
let posthog = PHGPostHog.shared()

PostHog is available for install via Pub.

Setup your Android, iOS, and/or web sources as described at PostHog.com and generate your API keys.

Set your PostHog API key and change the automatic event tracking (only for Android and iOS) on if you wish the library to take care of it for you.

Remember that the application lifecycle events won't have any special context set for you by the time it is initialized. If you are using a self-hosted instance of PostHog you will need to have the public hostname or IP for your instance as well.

Android

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.posthog.posthog_flutter_example">
<application>
<activity>
[...]
</activity>
<meta-data android:name="com.posthog.posthog.API_KEY" android:value="<ph_project_api_key>" />
<meta-data android:name="com.posthog.posthog.POSTHOG_HOST" android:value="<ph_instance_address>" />
<meta-data android:name="com.posthog.posthog.TRACK_APPLICATION_LIFECYCLE_EVENTS" android:value="false" />
<meta-data android:name="com.posthog.posthog.DEBUG" android:value="false" />
</application>
</manifest>

iOS

Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
[...]
<key>com.posthog.posthog.API_KEY</key>
<string><ph_project_api_key></string>
<key>com.posthog.posthog.POSTHOG_HOST</key>
<string><ph_instance_address></string>
<key>com.posthog.posthog.TRACK_APPLICATION_LIFECYCLE_EVENTS</key>
<false/>
<false/>
[...]
</dict>
</plist>

Web

HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>example</title>
</head>
<body>
<script>
!function () {
var analytics = window.analytics = window.analytics || []; if (!analytics.initialize) if (analytics.invoked) window.console && console.error && console.error("PostHog snippet included twice."); else {
analytics.invoked = !0; analytics.methods = ["captureSubmit", "captureClick", "captureLink", "captureForm", "pageview", "identify", "reset", "group", "capture", "ready", "alias", "debug", "page", "once", "off", "on"]; analytics.factory = function (t) { return function () { var e = Array.prototype.slice.call(arguments); e.unshift(t); analytics.push(e); return analytics } }; for (var t = 0; t < analytics.methods.length; t++) { var e = analytics.methods[t]; analytics[e] = analytics.factory(e) } analytics.load = function (t, e) { var n = document.createElement("script"); n.type = "text/javascript"; n.async = !0; n.src = "https://cdn.posthog.com/analytics.js/v1/" + t + "/analytics.min.js"; var a = document.getElementsByTagName("script")[0]; a.parentNode.insertBefore(n, a); analytics._loadOptions = e }; analytics.SNIPPET_VERSION = "4.1.0";
analytics.load("YOUR_API_KEY_GOES_HERE");
analytics.page();
}
}();
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>

For more information please check: https://posthog.com/docs/integrate/client/js

In your React Native or Expo project add the posthog-react-native package to your project as well as the required peer dependencies.

Expo Apps

sh
expo install posthog-react-native expo-file-system expo-application expo-device expo-localization

React Native Apps

sh
yarn add posthog-react-native @react-native-async-storage/async-storage react-native-device-info
# or
npm i -s posthog-react-native @react-native-async-storage/async-storage react-native-device-info

With the PosthogProvider

The recommended flow for setting up PostHog React Native is to use the PostHogProvider which utilises the Context API to pass the PostHog Client around as well as enabling autocapture and ensuring that the queue is flushed at the right time:

JSX
// App.(js|ts)
import { usePostHog, PostHogProvider } from 'posthog-react-native'
...
export function MyApp() {
return (
<PostHogProvider apiKey="<ph_project_api_key>" options={{
// (Optional) PostHog API host (https://app.posthog.com by default)
host: '<ph_instance_address>',
}}>
<MyComponent />
</PostHogProvider>
)
}
// Now you can simply access posthog elsewhere in the app like so
const MyComponent = () => {
const posthog = usePostHog()
useEffect(() => {
posthog.capture("MyComponent loaded", { foo: "bar" })
}, [])
}

Without the PosthogProvider

Due to the Async nature of React Native, PostHog needs to be initialised asynchronously in order for the persisted state to be loaded properly. The PosthogProvider takes care of this under-the-hood but you can alternatively create the instance yourself like so:

TSX
// posthog.ts
import PostHog from 'posthog-react-native'
export let posthog: PostHog | undefined = undefined
export const posthogAsync: Promise<PostHog> = PostHog.initAsync('<ph_project_api_key>', {
// PostHog API host (https://app.posthog.com by default)
host: '<ph_instance_address>'
})
posthogAsync.then(client => {
posthog = client
})
// app.ts
import { posthog, posthogAsync} from './posthog'
export function MyApp1() {
useEffect(async () => {
// Use posthog optionally with the possibility that it may still be loading
posthog?.capture('MyApp1 loaded')
// OR use posthog via the promise
(await posthogAsync).capture('MyApp1 loaded')
}, [])
return <View>Your app code</View>
}
// You can even use this instance with the PostHogProvider
export function MyApp2() {
return <PostHogProvider client={posthogAsync}>{/* Your app code */}</PostHogProvider>
}

Configuration options

For most people, the default configuration options will work great but if you need to you can further customise how PostHog will work.

TypeScript
export const posthog = await PostHog.initAsync("<ph_project_api_key>", {
// PostHog API host
host?: string = "https://app.posthog.com",
// The number of events to queue before sending to PostHog (flushing)
flushAt?: number = 20,
// The interval in milliseconds between periodic flushes
flushInterval?: number = 10000
// If set to false, tracking will be disabled until `optIn` is called
enable?: boolean = true,
// Whether to track that `getFeatureFlag` was called (used by Expriements)
sendFeatureFlagEvent?: boolean = true,
// Whether to load feature flags when initialised or not
preloadFeatureFlags?: boolean = true,
// How many times we will retry HTTP requests
fetchRetryCount?: number = 3,
// The delay between HTTP request retries
fetchRetryDelay?: number = 3000,
// For Session Analysis how long before we expire a session
sessionExpirationTimeSeconds?: number = 1800 // 30 mins
// Whether to post events to PostHog in JSON or compressed format
captureMode?: 'json' | 'form'
})

Use autocapture

Autocapture is presently only available in the web browser using the JavaScript library. For non-browser platforms and for situations where you wish to manually capture events, see the capture user events section.

When you call posthog.init the PostHog JS library begins automatically capturing user events:

  • pageviews, including the URL
  • autocaptured events, such as any click, change of input, or submission associated with a, button, form, input, select, textarea, and label tags

Autocapture allows you to track the majority of general events on your website right out of the gate

PostHog puts a great amount of effort into making sure it doesn't capture any sensitive data from your website. If there are other elements you don't want to be captured, you can add the ph-no-capture class name.

HTML
<button class='ph-no-capture'>Sensitive information here</button>

Capture user events

This allows you to send more context than the default event info that PostHog captures whenever a user does something. In that case, you can send an event with any metadata you may wish to add.

posthog.capture('[event-name]', { property1: 'value', property2: 'another value' })

A capture call requires:

  • event to specify the event name
    • We recommend naming events with "[noun][verb]", such as movie played or movie updated, in order to easily identify what your events mean later on (we know this from experience).

Optionally you can submit:

  • properties, which is an object with any information you'd like to add

Identify users

To make sure you understand which user is performing actions within your app, you can identify users at any point. From the moment you make this call, all events will be identified with that distinct id.

The ID can by anything, but is usually the unique ID that you identify users by in the database. Normally, you would put this below posthog.init if you have the information there.

If a user was previously anonymous (because they hadn't signed up or logged in yet), we'll automatically alias their anonymous ID with their new unique ID. That means all their events from before and after they signed up will be shown under the same user.

JavaScript
posthog.identify(
'[user unique id]', // distinct_id, required
{ userProperty: 'value1' }, // $set, optional
{ anotherUserProperty: 'value2' } // $set_once, optional
);

You can also pass two more arguments to posthog.identify. Both take objects consisting of as many properties as you want to be set on that user's profile. The difference is that the second argument will use the $set mechanism, whereas the third argument will use $set_once.

You can read more about the difference between this in the setting properties section.

Warning! You can't call identify straight after an `.init` (as `.init` sends a pageview event, probably with the user's anonymous ID).

To address the issue described above, you can call .init passing a loaded callback function, inside which you can then call identify, like so:

JavaScript
posthog.init('<ph_project_api_key>', {
api_host: '<ph_instance_address>',
loaded: function(posthog) { posthog.identify('[user unique id]'); }
});

When you start tracking events with PostHog, each user gets an anonymous ID that is used to identify them in the system. In order to link this anonymous user with someone from your database, use the identify call.

Identify lets you add metadata to your users so you can easily identify who they are in PostHog, as well as do things like segment users by these properties.

An identify call requires:

  • distinctId which uniquely identifies your user in your database
  • userProperties with a dictionary of key:value pairs
Java
PostHog.with(this)
.identify(distinctID, new Properties()
.putValue("name", "My Name")
.putValue("email", "user@posthog.com"));

The most obvious place to make this call is whenever a user signs up, or when they update their information.

When you call identify, all previously tracked anonymous events will be linked to the user.

When you start tracking events with PostHog, each user gets an anonymous ID that is used to identify them in the system. In order to link this anonymous user with someone from your database, use the identify call.

Identify lets you add metadata to your users so you can easily identify who they are in PostHog, as well as do things like segment users by these properties.

An identify call requires:

  • distinct_id which uniquely identifies your user in your database
  • properties with a dictionary of key:value pairs

For example:

Objective-C
// in objective-c
[[PHGPostHog sharedPostHog] identify:@"distinct_id_from_your_database"
properties:@{ @"name": @"Peter Griffin",
@"email": @"peter@familyguy.com" }];
Swift
// in swift
posthog.identify("user_id_from_your_database",
properties: ["name": "Peter Griffin", "email": "peter@familyguy.com"])

The most obvious place to make this call is whenever a user signs up, or when they update their information.

When you call identify, all previously tracked anonymous events will be linked to the user.

See the Flutter library docs for more information.

When you start tracking events with PostHog, each user gets an anonymous ID that is used to identify them in the system. In order to link this anonymous user with someone from your database, use the identify call.

Identify lets you add metadata on your users so you can more easily identify who they are in PostHog, and even do things like segment users by these properties.

An identify call requires:

  • distinctId which uniquely identifies your user in your database
  • userProperties with a dictionary with key: value pairs
JavaScript
posthog.identify('distinctID', {
email: 'user@posthog.com',
name: 'My Name'
})

The most obvious place to make this call is whenever a user signs up, or when they update their information.

When you call identify, all previously tracked anonymous events will be linked to the user.

Event ingestion nuances

It's a priority for us that events are fully processed and saved as soon as possible. However, there is a class of events which we deliberately process with a slight delay. Specifically, an event is delayed by around a minute if it fits all of the following three conditions:

  • isn't from an anonymous user (anonymous users are recognized by having the distinct_id the same as the $device_id property)
  • isn't an $identify event (e.g. from posthog.identify())
  • its distinct_id cannot be matched to an existing person

This delay mechanism is called the event buffer, and it materially improves handling of an edge case which could otherwise inflate unique user counts.

How does the event buffer help?

Starting with version 1.38.0, PostHog stores the person associated with an event inline with the event record. This greatly improves query performance, but because events are immutable, it also means that persons can't be merged retroactively. See this scenario where that's problematic:

  1. User visits signup page, in turn frontend captures anonymous $pageview for distinct ID XYZ (anonymous distinct ID = device ID).
    This event gets person ID A.
  2. User click signup button, initiating in a backend request, in turn frontend captures anonymous $autocapture (click) for distinct ID XYZ.
    This event gets person ID A.
  3. Signup request is processed in the backend, in turn backend captures identified signup for distinct ID alice@example.com.
    OOPS! We haven't seen alice@example.com before, so this event gets person ID B.
  4. Signup request finishes successfully, in turn frontend captures identified $identify aliasing distinct ID XYZ to alice@example.com.
    This event gets person ID A.

Here, the event from step 3 got a new person ID B, impacting unique users counts. If it were delayed just a bit and processed after the event from step 4, all events would get the expected person ID A. This is exactly what the event buffer achieves.

Questions?

Was this page useful?

Next article

Ingest historical data

Historical data ingestion (or importing data), opposed to live data ingestion , is the process of transporting data from external sources into PostHog so you can benefit from PostHog product analytics on historical data. It may be that you have historical data that you want to analyze along with new live data or that you have a requirement to periodically import data from third-party sources to augment your live data. Whatever the reason for the historical data ingestion, this guide covers what…

Read next article