Surveys + annotations
Two flows pulling opposite directions. Surveys → Signals brings PostHog evidence into Findry. Findry events → annotations stamps Findry decisions onto every PostHog metric chart.
Contents
Both directions of the loop
PostHog is bidirectional with Findry. Survey responses flow in as Signals you can link to hypotheses. Findry decisions flow out as annotations on the PostHog timeline. Together they make the analytics loop legible from either end.
PMs scrolling a PostHog metric chart see vertical lines marking every Findry decision: which bet promoted, which bet shipped, which outcome verdict landed. The spike or dip lines up with the bet that caused it. PMs scrolling the Findry signals list see which signals came from PostHog surveys with full provenance.
Surveys → Signals (the inbound flow)
PostHog Surveys collect first-party qualitative responses (in-app prompts, NPS widgets, beta-feedback forms). Without this importer, those responses live in PostHog's Surveys UI and are invisible to Findry. After enabling: every nightly sweep pulls new responses since the last cursor and inserts them assource_type = 'survey' Signals.
Enable a survey
Settings → Integrations → PostHog tile → Surveys- The table loads detected surveys from PostHog on mount (single call to
GET /api/projects/N/surveys/). - Pick the destination Findry project (defaults to the workspace's first project — most workspaces have one anyway).
- Click Enable for the surveys you want imported.
- The next nightly sweep at 07:15 UTC runs the import.
Per-survey opt-in. Enabling a survey doesn't enable the others — each is its own toggle. This matters because PostHog projects often have a dozen old surveys you never want imported.
Where responses land
Each PostHog survey sent event becomes one Signal:
id— generatedsig_*workspace_id+project_id— the workspace + per-survey routing you pickedsource_type—"survey"source_participant— PostHogdistinct_id(or"PostHog respondent"if anonymous)signal_date— the event's timestamp (when the response was submitted)content— the response body, flattened to a string. Multi-question surveys come through as concatenated answers; single-text surveys are passed through verbatimsource_external_id—posthog:<event-uuid>, the idempotency keyimport_metadata— jsonb carrying full provenance:{ provider, integrationId, surveyId, surveyName, responseId, distinctId, submittedAt, rawResponse, questions }
In the Signals list, imported surveys show with an Imported chip + a per-card provenance line: "PostHog survey · 'NPS Q2' · respondent user_42". Filter the Signals list by source_type = survey to see only imported rows.
Pause + resume
Click Disable on any enabled survey. The cursor (last_imported_at) is preserved — re-enabling resumes from where it left off. No re-import of historical responses.
If you want to re-import a survey from scratch (e.g. you accidentally enabled it pointed at the wrong project), you'd need to manually clear the cursor in the database or contact paulo@findry.io. Most workflows don't need this.
Per-survey volume cap:500 responses per survey per nightly run. If a workspace exceeds it (rare), the next run picks up the rest the following day. Surveys with sustained >500/day response rates aren't a target Findry persona today.
Annotations → PostHog (the outbound flow)
When a Bet is promoted, ships, or its outcome verdict lands, Findry drops a labeled vertical line on every PostHog metric chart in the project. This is the visual closure of the loop — PMs scanning a chart see immediately which spike or dip lines up with which Findry experiment.
Defaults ON when PostHog is connected. PMs already trusted Findry with the API key, and annotations are a natural extension of the loop-closure promise.
What fires an annotation
Three events, fired inline at the moment the state changes:
- bet.promoted — when you promote a hypothesis to a bet from the hypothesis detail page. Annotation content:
▲ Findry · Bet promoted (Project Name) — <hypothesis statement>at promotion time - bet.shipped — when the bet status flips to
shipped(manually OR auto-detected via Linear/Jira ticket status webhook). Annotation:▲ Findry · Bet shipped (Project Name) — <statement>at the ship date - outcome.verdict — when a verdict is assigned (automatically by the outcome-sweep cron OR manually overridden on the outcome detail page). Annotation:
● Findry · Outcome verdict (Project Name) — <statement>at the measurement timestamp
Long hypothesis statements get truncated to 120 chars + ellipsis. The triangle / dot prefix is the visual hook PMs scanning a PostHog chart rely on to spot Findry's annotations vs PostHog's native ones.
If PostHog has the annotation deleted underneath us (rare — the PM clicked delete in PostHog), the next push detects the 404 and creates a fresh annotation, repointing Findry's tracking row.
Toggle off + opt-out
Settings → Integrations → PostHog tile → Push annotations toggle. Flipping off is silent — existing annotations in PostHog stay put as historical context. Only future events are suppressed.
Why default ON: PMs who connect PostHog already trust Findry to read their data, and annotations are pure write to a low-stakes surface. Opting out is a single click; opting in for every workspace is friction nobody asked for.
Failure + audit
The push is fire-and-forget by design. A PostHog outage, an invalid annotation payload, or a 5xx from PostHog must never block a bet promotion / ship / verdict write — that's the user's transition succeeding, the annotation is just a side-effect.
Every push attempt records an audit event in the workspace audit log:
posthog.annotation_pushed— successful push (withcreated/updated/recreatedflag in metadata)posthog.annotation_failed— error path (with the error message + HTTP status if any)
Surface the audit log at Settings → Audit. Filter by the two action names above to see every annotation attempt + its outcome. Failures also surface in stderr via Vercel logs as[posthog-annotations/<event>] failed — swallowing.
If you ever want to wipe Findry's annotation tracking and re-push from scratch (e.g. after a major PostHog project migration), theposthog_annotation_pushes table is the single source of truth for "what's already been pushed" — clearing rows there makes the next bet event create a brand-new annotation. Talk to paulo@findry.io before doing this in prod.