Findry
Get started
Platform

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.

9 min

Contents
PostHog deep dives
On this page

Both directions of the loop

Bidirectional flow between PostHog and FindryPOSTHOGSurveysNPS · feedbackin-app promptsTimelinemetric charts+ annotationsFINDRYSignalsimported withfull provenanceDecisionspromoted · shippedverdict assignedSURVEY → SIGNALNIGHTLY · 07:15 UTCDECISION → ANNOTATIONREAL-TIME · IDEMPOTENT
Two opposite flows pulling against each other — both close 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

  1. Settings → Integrations → PostHog tile → Surveys
  2. The table loads detected surveys from PostHog on mount (single call to GET /api/projects/N/surveys/).
  3. Pick the destination Findry project (defaults to the workspace's first project — most workspaces have one anyway).
  4. Click Enable for the surveys you want imported.
  5. 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 — generated sig_*
  • workspace_id + project_id — the workspace + per-survey routing you picked
  • source_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 verbatim
  • source_external_idposthog:<event-uuid>, the idempotency key
  • import_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 toshipped (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 / recreated flag 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.

PreviousMetrics + mappingsUp nextCohorts, flags + routing