PostHog
Datafly Signal delivers events to PostHog server-to-server using the capture API (/i/v0/e/). Events are sent directly from your Signal deployment to PostHog, so delivery is unaffected by ad blockers, browser tracking prevention, or client-side script failures.
Why route PostHog through Datafly Signal
- Your numbers stop depending on what the browser allows. Client-side analytics loses every visit where the tag is blocked or the page unloads early. Signal delivers from the server, so PostHog receives the complete event stream.
- Client IP and user agent are carried in the payload. Both are taken from the original visitor request rather than your server, which is what PostHog needs to attribute the event to the right session.
- One consent decision governs every destination. PostHog is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.
Prerequisites
Before configuring PostHog in Signal, you need a PostHog project and its Project API Key.
Create or open a PostHog project
- Sign in at PostHog (US Cloud) or eu.posthog.com (EU Cloud), or open your self-hosted instance.
- Create a project if you don’t already have one.
Find your Project API Key
- Go to Settings > Project.
- Copy the Project API Key (a write-only token that starts with
phc_).
The Project API Key is a public, write-only token. It can only send events — it cannot read data from your project — so it is safe to use as the capture credential.
Note your data region
PostHog Cloud runs in two independent regions. Your project lives in exactly one of them:
- US Cloud — ingestion host
us.i.posthog.com - EU Cloud — ingestion host
eu.i.posthog.com
For self-hosted PostHog, use your own instance host.
The region must match the project. A US token sent to the EU host (or vice versa) is silently rejected — events return a success-shaped response but never appear in your project.
Configure in Signal
| Field | Required | Description |
|---|---|---|
project_api_key | Yes | Your PostHog Project API Key (starts with phc_). Settings > Project > Project API Key. |
ingestion_host | Yes | Your data region: us.i.posthog.com, eu.i.posthog.com, or your self-hosted host. |
Management UI Setup
- Go to Integrations > Add Integration > PostHog.
- Enter your
project_api_key. - Select your Ingestion Host (data region).
- Select consent categories (typically
analytics). - Click Save.
API Endpoint
POST https://us.i.posthog.com/i/v0/e/ # US Cloud
POST https://eu.i.posthog.com/i/v0/e/ # EU CloudEach event is sent as a single JSON object. The Project API Key travels in the request body as api_key — there is no Authorization header.
{
"api_key": "phc_xxxxxxxxxxxxxxxxxxxx",
"event": "$pageview",
"distinct_id": "user-123",
"properties": {
"$current_url": "https://example.com/pricing"
},
"timestamp": "2026-06-06T12:00:00.000Z"
}Identity Signals
PostHog identifies users by a distinct_id that must be present on every event.
| Signal | Field | Description |
|---|---|---|
distinct_id | distinct_id | The logged-in user ID (user_id) when available, falling back to Signal’s anonymous_id for anonymous traffic. |
| Anonymous ID | properties.$anon_distinct_id | Signal’s anonymous_id, so PostHog can stitch pre-login activity to the identified person. |
| IP address | properties.$ip | Visitor IP, forwarded from the original request, so PostHog resolves GeoIP server-side. |
| Library | properties.$lib | Identifies the sending library. |
Person properties
When a user is identified via datafly.identify() with traits, Signal attaches them to PostHog’s person profile using PostHog’s $set and $set_once conventions:
| Trait | Target | Behaviour |
|---|---|---|
email | properties.$set.email | $set overwrites on every event. |
name | properties.$set.name | $set overwrites on every event. |
phone | properties.$set.phone | $set overwrites on every event. |
email (initial) | properties.$set_once.initial_email | $set_once is written only the first time. |
PostHog stores person properties as raw values (it has no hashed-PII matching model). Signal sends email, name, and phone in clear under $set. Delivery is gated by the pipeline’s consent categories — events are only sent when the visitor’s consent allows the configured category (typically analytics).
datafly.identify("user-123", {
email: "jane.doe@example.com",
name: "Jane Doe",
phone: "+44 7700 900123"
});Event Mapping
Signal’s GA4-style event names are mapped to PostHog snake_case event names. PostHog’s reserved $pageview event powers its web-analytics paths and referrers report.
| Signal event | PostHog event |
|---|---|
page | $pageview |
Signed Up | signed_up |
Logged In | logged_in |
Products Searched | products_searched |
Product Viewed | product_viewed |
Product Added | product_added |
Checkout Started | checkout_started |
Order Completed | order_completed |
Any event not in the map is dropped (the blueprint’s default action is drop). To customise, edit the integration’s Field Mappings in the Management UI.
Example: Order Completed → order_completed
Datafly.js call:
datafly.track("Order Completed", {
order_id: "ORD-001",
revenue: 129.99,
currency: "USD",
products: [
{ product_id: "SKU-A", name: "Widget", price: 49.99, quantity: 2 },
{ product_id: "SKU-B", name: "Gadget", price: 30.01, quantity: 1 }
]
});PostHog payload sent by Signal:
{
"api_key": "phc_xxxxxxxxxxxxxxxxxxxx",
"event": "order_completed",
"distinct_id": "user-123",
"timestamp": "2026-06-06T12:00:00.000Z",
"properties": {
"$ip": "203.0.113.50",
"$lib": "datafly-js",
"$anon_distinct_id": "01HX8…",
"order_id": "ORD-001",
"revenue": 129.99,
"currency": "USD",
"products": [
{ "product_id": "SKU-A", "name": "Widget", "price": 49.99, "quantity": 2 },
{ "product_id": "SKU-B", "name": "Gadget", "price": 30.01, "quantity": 1 }
],
"$set": { "email": "jane.doe@example.com", "name": "Jane Doe" }
}
}Testing Your Integration
Trigger events
Browse your site or call datafly.track() / datafly.identify() to generate events.
Watch the PostHog Activity view
- In PostHog, open Activity (the live events feed).
- New events should appear within seconds, tagged with their
distinct_idand properties.
Verify person properties
- Open People and search for the
distinct_id. - Confirm
email/nameappear under the person’s properties after anidentifycall.
Check web analytics
- Open Web analytics.
- Confirm page views populate paths and referrers — these come from
$pageviewwith$current_urland$referrer.
Troubleshooting
| Problem | Solution |
|---|---|
| Events not appearing | Confirm the ingestion host matches the project’s region (US token to US host, EU to EU). A region mismatch is silently rejected. |
| Events accepted but person not identified | Ensure distinct_id is stable — events for the same user must share the same distinct_id. Check the user_id → anonymous_id fallback resolves. |
| GeoIP / location missing | Confirm $ip is being forwarded; PostHog resolves location from the IP property server-side. |
| Person properties not updating | Verify traits are sent via datafly.identify() and land under properties.$set. |
| Page views missing from web analytics | Confirm the page event maps to $pageview and carries $current_url. |
Notes
- PostHog’s capture endpoint returns HTTP
200with{"status": 1}on accept. A bad or missingapi_keyalso returns200, but with{"status": 0}— Signal treats the latter as a failed delivery, so misconfigured credentials surface in the event debugger rather than passing silently. - Self-hosted PostHog is supported: set the ingestion host to your own instance.