The /i/v0/e and /batch endpoints are the main way to send events to PostHog. Beyond user behavior, they are also used to identify users, update person or group properties, migrate from other platforms, and more. Our SDKs handle the different event types for you, but with the API, you need to send the right type of event (listed below) to trigger the functionality you want.
Both are POST-only public endpoints that use your project API key and do not return any sensitive data from your PostHog instance.
Note: Make sure to send API requests to the correct domain. These are https://us.i.posthog.com for US Cloud, https://eu.i.posthog.com for EU Cloud, and your self-hosted domain for self-hosted instances. Confirm yours by checking your URL from your PostHog instance.
Here are examples of the types of events you can send:
Every event request must contain an api_key, distinct_id, and event field with the name. Both the properties and timestamp fields are optional.
Note: By default, events captured via the API are identified events . See the section on how to capture anonymous events for more.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "event name",
"distinct_id": "user distinct id",
"properties": {
"account_type": "pro"
},
"timestamp": "[optional timestamp in ISO 8601 format]"
}' https://us.i.posthog.com/i/v0/e/
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "event name" ,
"distinct_id" : "user distinct id" ,
"properties" : {
"account_type" : "pro"
} ,
"timestamp" : "[optional timestamp in ISO 8601 format]"
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response )
import fetch from "node-fetch" ;
async function sendPosthogEvent ( ) {
const url = "https://us.i.posthog.com/i/v0/e/" ;
const headers = {
"Content-Type" : "application/json" ,
} ;
const payload = {
api_key : "<ph_project_api_key>" ,
event : "event name" ,
distinct_id : "user distinct id" ,
properties : {
account_type : "pro" ,
} ,
timestamp : "[optional timestamp in ISO 8601 format]" ,
} ;
const response = await fetch ( url , {
method : "POST" ,
headers : headers ,
body : JSON . stringify ( payload ) ,
} ) ;
const data = await response . json ( ) ;
console . log ( data ) ;
}
sendPosthogEvent ( )
To capture anonymous events , add the $process_person_profile property to the event and set it to false.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "event name",
"distinct_id": "user distinct id",
"properties": {
"$process_person_profile": false
},
"timestamp": "2020-08-16T09:03:11.913767"
}' https://us.i.posthog.com/i/v0/e/
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "event name" ,
"distinct_id" : "user distinct id" ,
"properties" : {
"$process_person_profile" : False
}
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response )
import fetch from "node-fetch" ;
async function sendAnonymousPosthogEvent ( ) {
const url = "https://us.i.posthog.com/i/v0/e/" ;
const headers = {
"Content-Type" : "application/json" ,
} ;
const payload = {
api_key : "<ph_project_api_key>" ,
event : "event name" ,
distinct_id : "user distinct id" ,
properties : {
$process_person_profile : false
}
} ;
const response = await fetch ( url , {
method : "POST" ,
headers : headers ,
body : JSON . stringify ( payload ) ,
} ) ;
const data = await response . json ( ) ;
console . log ( data ) ;
}
sendAnonymousPosthogEvent ( )
You can capture multiple events in one request with the /batch API route. There is no limit on the number of events you can send in a batch, but the entire request body must be less than 20MB by default.
This example shows how the required distinct_id value can also be passed in properties.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"historical_migration": false,
"batch": [
{
"event": "batched_event_name_1",
"properties": {
"distinct_id": "user distinct id",
"account_type": "pro"
},
"timestamp": "[optional timestamp in ISO 8601 format]"
},
{
"event": "batched_event_name_2",
"properties": {
"distinct_id": "user distinct id",
"account_type": "pro"
}
}
]
}' https://us.i.posthog.com/batch/
import requests
import json
url = "https://us.i.posthog.com/batch/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"historical_migration" : False ,
"batch" : [
{
"event" : "batched_event_name_1" ,
"properties" : {
"distinct_id" : "user distinct id" ,
"account_type" : "pro"
} ,
"timestamp" : "[optional timestamp in ISO 8601 format]"
} ,
{
"event" : "batched_event_name_2" ,
"properties" : {
"distinct_id" : "user distinct id" ,
"account_type" : "pro"
}
}
]
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response . text )
The historical_migration field is optional. For realtime events, it can be left out or set to false.
When running migrations , the historical_migration field must be set to true. This ensures that events are processed in order without triggering our spike detection systems.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"historical_migration": true,
"batch": [
{
"event": "batched_event_name",
"properties": {
"distinct_id": "user_id"
},
"timestamp": "2024-04-03T12:00:00Z"
},
{
"event": "batched_event_name",
"properties": {
"distinct_id": "user_id"
},
"timestamp": "2024-04-03T12:00:00Z"
}
]
}' https://us.i.posthog.com/batch/
# The Python SDK also support historical migrations
# See: /docs/libraries/python#historical-migrations
import requests
import json
url = "https://us.i.posthog.com/batch/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"historical_migration" : True ,
"batch" : [
{
"event" : "batched_event_name" ,
"properties" : {
"distinct_id" : "user_id" ,
} ,
"timestamp" : "2024-04-03T12:00:00Z"
} ,
{
"event" : "batched_event_name" ,
"properties" : {
"distinct_id" : "user_id"
} ,
"timestamp" : "2024-04-03T12:00:00Z"
}
]
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response . text )
This assigns another distinct ID to the same user. Read more in our identify docs .
In this example, 123 is merged into 456 and 456 becomes the main distinct_id for events associated with 123.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "$create_alias",
"distinct_id": "123",
"properties": {
"alias": "456"
}
}' https://us.i.posthog.com/i/v0/e/
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "$create_alias" ,
"distinct_id" : "123" ,
"properties" : {
"alias" : "456"
}
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response )
Updates a group's information or creates one if it does not exist. Read more in our group analytics docs .
$group_type must be at most 400 characters long$group_key must be at most 400 characters longcurl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "$groupidentify",
"distinct_id": "groups_setup_id",
"properties": {
"$group_type": "<group_type>",
"$group_key": "<company_name>",
"$group_set": {
"name": "<company_name>",
"subscription": "premium"
"date_joined": "[optional timestamp in ISO 8601 format]"
}
}
}' https://us.i.posthog.com/i/v0/e/
import json
import requests
from datetime import datetime
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "$groupidentify" ,
"distinct_id" : "groups_setup_id" ,
"properties" : {
"$group_type" : "<group_type>" ,
"$group_key" : "<company_name>" ,
"$group_set" : {
"name" : "<company_name>" ,
"subscription" : "premium" ,
"date_joined" : datetime . now ( ) . isoformat ( )
}
}
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response . text )
Captures event with a group. In this case, company is a group type . You can set it to the value you want such as organization, project, or channel. Read more in our group analytics docs .
Note: This event will not create a new group if a new key being used. To create a group, see the group identify event.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "event name",
"distinct_id": "user distinct id",
"properties": {
"$groups": {"company": "<company_name>"}
}
}' https://us.i.posthog.com/i/v0/e/
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "event name" ,
"distinct_id" : "user distinct id" ,
"properties" : {
"account_type" : "pro" ,
"$groups" : { "company" : "<company_name>" }
}
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response )
Updates person properties. Read more in our identify docs .
Note: The $identify event works differently from the identify() method in the JavaScript SDK . This event updates the person properties, while the JavaScript identify() method connects an anonymous user and a distinct ID.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "$identify",
"distinct_id": "user distinct id",
"properties": {
"$set": {
"is_cool": "true"
}
},
"timestamp": "2020-08-16T09:03:11.913767"
}' https://us.i.posthog.com/i/v0/e/
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "$identify" ,
"distinct_id" : "user distinct id" ,
"properties" : {
"$set" : {
"is_cool" : True
}
}
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response )
PageviewDefault PostHog events and properties have the $ prefix. The most common and popular of these is the $pageview event.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "$pageview",
"distinct_id": "user distinct id",
"properties": {
"$current_url": "/docs/api/capture",
"$session_id": "019a0ccb-408c-728a-9df9-1ef51b742b36"
}
}' https://us.i.posthog.com/i/v0/e/
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "$pageview" ,
"distinct_id" : "user distinct id" ,
"properties" : {
"$current_url" : "/docs/api/capture" ,
"$session_id" : "019a0ccb-408c-728a-9df9-1ef51b742b36"
}
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response )
Note: For a server-side integration with Web Analytics, check out our Server SDKs and Sessions guide to make sure you're capturing every event and session property required for our product to work.
The equivalent of a pageview for mobile apps.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "$screen",
"distinct_id": "user distinct id",
"properties": {
"$screen_name": "TheScreen"
}
}' https://us.i.posthog.com/i/v0/e/
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "$screen" ,
"distinct_id" : "user distinct id" ,
"properties" : {
"$screen_name" : "TheScreen" ,
}
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response )
Although there is a lot of functionality you miss out on if you only use the API, you can capture survey related events. These include survey sent, survey shown, and survey dismissed, each of which requires $survey_id as a property. Read our docs on implementing custom surveys for more information.
curl -v -L --header "Content-Type: application/json" -d '{
"api_key": "<ph_project_api_key>",
"event": "survey sent",
"distinct_id": "user distinct id",
"properties": {
"$survey_id": "survey_id",
"$survey_response_d8462827-1575-4e1e-ab1d-b5fddd9f829c": "Awesome!",
"$survey_questions": [
{
"id": "d8462827-1575-4e1e-ab1d-b5fddd9f829c",
"question": "How likely are you to recommend us to a friend?"
}
]
}
}' https://us.i.posthog.com/i/v0/e/
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type" : "application/json"
}
payload = {
"api_key" : "<ph_project_api_key>" ,
"event" : "survey sent" ,
"distinct_id" : "user distinct id" ,
"properties" : {
"$survey_id" : "survey_id" ,
"$survey_response_d8462827-1575-4e1e-ab1d-b5fddd9f829c" : "Awesome!" ,
"$survey_questions" : [
{
"id" : "d8462827-1575-4e1e-ab1d-b5fddd9f829c" ,
"question" : "How likely are you to recommend us to a friend?"
}
]
}
}
response = requests . post ( url , headers = headers , data = json . dumps ( payload ) )
print ( response )
We perform basic validation on the payload and project API key (api_key), returning a failure response if an error is encountered.
PostHog does not return an error to the client when the following happens:
An event does not have a name An event does not have the distinct_id field set The distinct_id field of an event has an empty value These three cases above cause the event to not be ingested, but you still receive a 200: OK response from PostHog.
This approach enables us to process events asynchronously if necessary, ensuring reliability and low latency for our event ingestion endpoints.