DEV Community

Cover image for Kiponos vs PostHog Feature Flags — Product Analytics Suite vs Operational Config Trees (Architecture)
Moshe Avdiel
Moshe Avdiel

Posted on • Originally published at github.com

Kiponos vs PostHog Feature Flags — Product Analytics Suite vs Operational Config Trees (Architecture)

Monday 08:53. Product shipped PostHog across the checkout funnel — feature flags for pricing_hero_v2, session replay for drop-off analysis, event capture wired to the product dashboard. Growth loves the single OSS suite. Then authorization alarms fire: processor timeouts climb, a card-testing ring hits BIN 424242, and the payments fleet needs fraud/thresholds/block_score at 71, resilience/payments/failure_rate_threshold at 24, and runtime/tomcat/max_threads raised across fourteen Spring Boot pods serving 15k auth evaluations per second.

The frontend lead suggests:

"PostHog has feature flags and multivariate config — add block_score as a flag payload. One SDK, one analytics plane."

The authorization tech lead refuses:

"PostHog belongs on product surfaces where we want capture, replay, and cohort flags. Our auth filter runs before any product context exists — I will not put an analytics SDK with network calls and event buffering on the money path."

PostHog is a powerful all-in-one product analytics platform — feature flags, session replay, funnels, cohorts, and open-source self-hosting for product teams who want one vendor. Kiponos.io is a live operational config hub — typed nested trees, WebSocket deltas, and local reads in Java Spring Boot 3 and Python. Run PostHog for what users see and how product learns; Kiponos for how payment and fraud services behave under attack.

The problem — analytics feature flags on the authorization hot path

Correct PostHog usage for product surfaces:

// Correct — product BFF with analytics context
PostHog posthog = new PostHog.Builder(apiKey).build();

boolean showHeroV2 = posthog.isFeatureEnabled(
        "pricing_hero_v2",
        customerId,
        Map.of("plan", planTier, "country", countryCode)
);

posthog.capture(customerId, "pricing_page_viewed", Map.of(
        "variant", showHeroV2 ? "v2" : "control"
));
Enter fullscreen mode Exit fullscreen mode

The anti-pattern appears when fraud and resilience keys land in PostHog flags:

// Anti-pattern — analytics SDK on saturated authorization filter
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class FraudAuthFilter implements Filter {

    private final PostHog posthog;

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
        // Runs at 15k TPS — before product session exists
        int blockScore = posthog.getFeatureFlagPayload(
                "fraud_block_score",
                "system-auth",
                Integer.class,
                85
        );
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Pain points:

  • Analytics SDK on auth path — event buffering, identify calls, and flag polling add latency and failure modes where none belong
  • Flags assume product identity — fraud thresholds are global system state, not a cohort multivariate payload
  • Session replay and funnels are irrelevant — circuit breakers do not need replay to decide when to open
  • No nested ops treeresilience/payments/partner_adyen/timeout_ms becomes flat flag key sprawl
  • Python fraud scoring workers — no shared ops tree with Java authorization without duplicating PostHog flag definitions
  • Self-hosted PostHog footprint — ClickHouse + Kafka + workers justified for product analytics; incident floats should not inflate the flag catalog

PostHog is excellent for product analytics with integrated feature flags. It is the wrong dependency on payment authorization and fraud hot paths.

What teams believe vs production reality

Belief Production reality
"One PostHog SDK simplifies the stack" Product analytics and auth-path floats have different latency and privacy models
"Feature flag payloads replace ops config" Built for cohort targeting, not nested platform trees
"OSS PostHog is free so use it everywhere" ClickHouse + pipeline ops are real cost — scope to product
"Multivariate flags handle all runtime values" block_score = 71 is not a checkout hero variant
"We will keep YAML for auth and PostHog for web" Now two config planes with no shared fraud tree across Java and Python

The Aha

PostHog decides which users see which product experiences and records how they behave. Kiponos holds operational knobs — fraud scores, circuit thresholds, pool sizes — with local reads on paths that must never carry analytics SDKs. Keep pricing_hero_v2 in PostHog with session replay and funnel metrics. Move block_score, failure_rate_threshold, and max_threads to Kiponos.

What Kiponos.io is alongside PostHog product analytics

Kiponos is a real-time configuration hub. SDKs connect via WebSocket, load profile ['fintech']['auth']['prod']['live'], and mirror the tree in memory. Edit in dashboard → delta → next getInt() in any pod — no restart, no flag redeploy, no analytics identify round-trip.

PostHog remains your product analytics and experimentation plane for surfaces where capture and replay add value. Kiponos becomes your ops config plane for payment, fraud, and resilience values that change during incidents — same hub for Java authorization APIs and Python scoring workers, zero analytics overhead on the hot path.

Architecture — PostHog product plane vs Kiponos ops hub

Architecture diagram

PostHog stays on product-facing paths with analytics context. Kiponos serves system-bound values on paths that must stay analytics-free.

Config tree — ops structure PostHog flags were not designed to hold

fraud/
  thresholds/
    block_score: 71
    review_score: 58
    velocity_per_hour: 28
    bin_attack_multiplier: 1.35
resilience/
  payments/
    failure_rate_threshold: 24
    wait_duration_open_ms: 32000
    permitted_calls_half_open: 6
  partner_adyen/
    timeout_ms: 2800
    slow_call_rate_threshold: 0.45
runtime/
  tomcat/
    max_threads: 260
    accept_count: 220
  hikari/
    maximum_pool_size: 48
    minimum_idle: 16
    connection_timeout_ms: 4200
ml/
  bin_velocity/
    batch_size: 256
    worker_concurrency: 10
    model_version: v4.1.0
posthog_bridge/
  # Document flags that stay on PostHog
  pricing_hero_v2: posthog_owned
  onboarding_checklist_v3: posthog_owned
Enter fullscreen mode Exit fullscreen mode

Java integration — fraud and circuits without analytics SDK

@Configuration
public class KiponosConfig {

    @Bean
    public Kiponos kiponos(
            @Value("${kiponos.team-id}") String teamId,
            @Value("${kiponos.access-key}") String accessKey,
            @Value("${kiponos.profile-path}") String profilePath) {
        return Kiponos.builder()
                .teamId(teamId)
                .accessKey(accessKey)
                .profilePath(profilePath)
                .build();
    }
}
Enter fullscreen mode Exit fullscreen mode
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class FraudAuthFilter implements Filter {

    private final Kiponos kiponos;

    public FraudAuthFilter(Kiponos kiponos) {
        this.kiponos = kiponos;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpReq = (HttpServletRequest) req;
        int riskScore = extractRiskScore(httpReq);
        int hourlyVelocity = extractVelocity(httpReq);

        var fraud = kiponos.path("fraud", "thresholds");
        int blockScore = fraud.getInt("block_score");
        int velocityLimit = fraud.getInt("velocity_per_hour");

        if (hourlyVelocity > velocityLimit || riskScore >= blockScore) {
            ((HttpServletResponse) res).sendError(HttpServletResponse.SC_FORBIDDEN);
            return;
        }
        chain.doFilter(req, res);
    }
}
Enter fullscreen mode Exit fullscreen mode

Bind Tomcat threads when ops raises capacity during a flash event:

@PostConstruct
void bindThreadKnobs() {
    kiponos.afterValueChanged(change -> {
        if (change.getPath().startsWith("runtime/tomcat/max_threads")) {
            tomcatConnectorCustomizer.setMaxThreads(change.getNewValueAsInt());
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

Product flag — keep PostHog where analytics and replay belong:

public PricingHero resolveHero(String customerId, String planTier) {
    boolean showV2 = posthog.isFeatureEnabled(
            "pricing_hero_v2",
            customerId,
            Map.of("plan", planTier));
    posthog.capture(customerId, "pricing_hero_resolved",
            Map.of("variant", showV2 ? "v2" : "control"));
    return showV2 ? PricingHero.VARIANT_B : PricingHero.CONTROL;
}
Enter fullscreen mode Exit fullscreen mode

Python integration — BIN velocity worker shares the fraud tree

import os
from kiponos import Kiponos

os.environ["KIPONOS_PROFILE"] = "['fintech']['auth']['prod']['live']"
kiponos = Kiponos.create_for_current_team()


def detect_bin_attack(transactions: list[dict], target_bin: str) -> bool:
    velocity_limit = kiponos.path("fraud", "thresholds").get_int("velocity_per_hour", 20)
    multiplier = kiponos.path("fraud", "thresholds").get_float("bin_attack_multiplier", 1.0)
    batch_size = kiponos.path("ml", "bin_velocity").get_int("batch_size", 256)

    bin_count = sum(1 for t in transactions if t.get("bin") == target_bin)
    effective_limit = int(velocity_limit * multiplier)
    return bin_count > effective_limit


def on_config_change(change):
    if change.path.startswith("fraud/thresholds/block_score"):
        log.info("block_score updated to %s", change.new_value)


kiponos.after_value_changed(on_config_change)
Enter fullscreen mode Exit fullscreen mode

PostHog has no natural home for Python BIN velocity workers and Java authorization filters sharing fraud/thresholds/block_score during a card-testing ring — and you should not add ClickHouse pipeline dependencies to either runtime for incident floats.

Real scenarios

Event PostHog alone PostHog + Kiponos
pricing_hero_v2 flag with session replay Native analytics suite Keep PostHog; unchanged
BIN attack — lower block score on auth path Analytics SDK on 15k TPS filter fraud/thresholds/block_score local read
Processor outage — tighten circuit Flag payload + fake system identity resilience/payments/failure_rate_threshold immediate
Flash sale — raise Tomcat threads Not the tool; no replay needed runtime/tomcat/max_threads with live bind
Python + Java aligned fraud thresholds Duplicate flag definitions One Kiponos profile, two SDKs
Auth filter must stay analytics-free Violates architecture Kiponos embedded SDK — zero capture overhead

Performance — analytics flags vs ops hub reads

  • PostHog isFeatureEnabled() — remote evaluation or local cache with analytics coupling — ideal for product surfaces with capture
  • PostHog on authorization filter — identify buffering, event queue, flag staleness — wrong latency contract at 15k TPS
  • Session replay infrastructure — valuable for checkout UX; zero relevance for circuit breaker math
  • Kiponos getInt() — pure in-memory path walk with no network on hot path
  • WebSocket deltas — one key change propagates without PostHog flag deploys or pod restarts
  • Privacy boundary — auth path stays free of product analytics identifiers; Kiponos carries ops values only

Honest comparison table

Criterion PostHog (OSS) Kiponos Honest verdict
Product analytics + funnels Core strength Not analytics PostHog for product learning
Feature flags + session replay Integrated suite Not a replay platform PostHog wins UX diagnostics
Self-hosted all-in-one Full stack option Managed hub — evaluate policy Depends on InfoSec
Numeric ops thresholds on auth path Flag payload workarounds First-class local reads Kiponos for money path
Nested cross-service config trees Flat flag keys Hierarchical paths Kiponos for platform ops
Hot-path read at 15k TPS Analytics SDK overhead Local cache, no capture Kiponos on authorization
Live pool / thread tuning Not designed for this afterValueChanged binds Kiponos for JVM knobs
Java + Python same ops hub Partial Both SDKs Kiponos for polyglot ops
Cohort multivariate flags Native Application concern PostHog for product
Operational cost ClickHouse + pipeline ops Team/hub pricing Scope each system narrowly

When not to use Kiponos

Use case Better tool
Product funnels, cohorts, and session replay PostHog
Feature flags tied to analytics events PostHog
Marketing site A/B tests with replay PostHog
Bootstrap secrets and DB passwords Vault / K8s Secrets
Infrastructure desired state GitOps / Terraform

Getting started (15 minutes) — keep PostHog for product analytics only

  1. Audit PostHog flag catalog: mark each as product surface flag vs misplaced ops knob.
  2. TeamPro at kiponos.io — profile ['fintech']['auth']['prod']['live'].
  3. Migrate three ops keys off PostHog flags: block_score, failure_rate_threshold, max_threads.
  4. Wire Java FraudAuthFilter and Python BIN velocity worker to the same profile; add Tomcat bind hook.
  5. Document RFC: "PostHog owns product analytics and surface flags; Kiponos owns payment/fraud/resilience config trees — no analytics SDK on auth path."

Further reading


Kiponos.io — PostHog for what users clicked and which hero they saw. Live hub for how hard fraud blocks without analytics on the auth path.

Top comments (0)