PostHog
Send content and community events from PostHog; Patterna keeps the distinct id and drops the rest.
PostHog already sees what people read and join. Point a PostHog destination at Patterna and those events become canonical events. Patterna keeps the distinct id only and drops person properties before anything is written.
Status: the PostHog destination is built and available since engine 0.2. It stays off for a tenant until an owner sets a PostHog token; without one the webhook accepts nothing.
Set up the destination#
In PostHog, open Data pipelines and choose New destination.
Pick the HTTP Webhook destination.
Set the URL to your tenant's webhook URL:
https://patterna-api.orven.am/v1/webhooks/posthogMethod
POST. Add the headerX-Patterna-Token: <token>with the token an owner set for this tenant (below). The destination never carries an API key. Leave the body as PostHog's default event payload (one event, or an array of events:uuid,event,distinct_id,timestamp,properties).Add an event filter so only the mapped events go out:
content_viewed content_engaged content_scroll_depth community_joined community_leftSave and enable the destination.
In Patterna, open Connectors → PostHog and press Test connection. Within a few minutes the card shows rows in and rows mapped.
How it authenticates: the destination sends an X-Patterna-Token header. An owner sets the token (24 to 200 characters of random data) with PATCH /settings (posthog_token); Patterna stores only its SHA-256 hash. Set an empty token to switch the destination off again. Events from the webhook are stored as unsigned. The answer is 202 with accepted, duplicates, skipped (events with no mapping) and dropped_fields.
Sending other events does no harm: they are counted and dropped. The filter just keeps traffic and noise down.
What maps to what#
| PostHog event | Canonical event | Dropped |
|---|---|---|
content_viewed | item_viewed, with content_type → collection | content_title, content_slug |
content_engaged | item_engaged | |
content_scroll_depth | folded into item_engaged | |
community_joined / community_left | same name | |
cyclus_visited, cyclus_action, breathing_*, *_location_*, $screen | not ingested | everything |
| category pages | not ingested | everything |
| person properties | not ingested | email, name |
item_engaged counts as a positive signal only when engaged_seconds is 20 or more. See Event types for weights.
content_viewed with content_type community_post becomes community_post_viewed with the post id only.
What is kept and what is dropped#
Kept:
- The distinct id, as the user id. Ids containing
@are rejected. The webhook replaces it with a Patterna-minted hash unless it is already an import-mintedu_…id (see the warning above); the batch export can do the same (below). See Consent modes for what each mode stores. - Event name, time, item or community id.
- From
$properties: OS, app version and locale only.
Dropped before the row is written:
- Person properties and
$set. - IP address.
- Every other
$property. - Titles and slugs.
Every drop is counted per field. The Connectors card shows the counts under "Fields dropped", so you can see that the rule ran. See Data we never store.
Alternative: batch export files#
If you prefer not to stream, export PostHog events to an S3 bucket or a local directory as JSONL (optionally gzipped). The jobs read those files on the connectors schedule (every 15 minutes, at the interval set for your tenant) and apply the same mapping. Parquet exports are not supported yet.
The batch reader can map ids: with id_mapping: "hmac" on the connector, each distinct id becomes u_ plus a keyed hash, so the raw distinct id is never written. The key sits in the jobs' environment (PATTERNA_ID_SECRET), which means Patterna, not you, could recompute the mapping from a known id. If that is not acceptable, hash on your side before the export, or send events from your backend with the Patterna id.
Check it works#
On Connectors → PostHog look at:
- Last run and status.
- Rows in and rows mapped. Rows in minus rows mapped is what was not ingested on purpose (for example
$screen). - Fields dropped, with counts per field.
- Errors, with the request id.
Then open Audiences → trace for a test user and confirm the events arrive.