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.
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.