Klaviyo Shopify Integration in Flutter: What to Know First
A Klaviyo Shopify integration into a Flutter e-commerce app — four workstreams, six PRs, and the one gap no app code can close.
For CTOs, tech leads, and senior developers evaluating a Klaviyo integration for a mobile e-commerce app — or about to scope one. If you’re looking for a step-by-step SDK installation guide, the Klaviyo docs cover that well. This post is about what happens after installation — the architecture, integration, and scoping decisions that determine whether the project takes two weeks or two months.
Klaviyo × Flutter series (part 1 of 4): planning & scope · the analytics layer · push notification pitfalls · profiles & newsletter.
TL;DR: The klaviyo_flutter_sdk installs in minutes. The production integration for a Shopify-based DTC brand took five weeks and six PRs. The single largest investment was a unified analytics abstraction layer that made Klaviyo a clean adapter plug-in — and the one gap no app-side code can close is purchase tracking, because checkout opens in the browser.
Why “install the SDK” is the wrong mental model
When a client asks about a Klaviyo Shopify integration for their Flutter app, the conversation usually starts with the SDK. “How long to install it?” The answer — an hour, maybe two — is technically correct and completely misleading.
The klaviyo_flutter_sdk wraps the native Klaviyo iOS and Android SDKs. It covers profile identification, event tracking, and push token registration. You add the dependency, call Klaviyo.initialize(apiKey), and the SDK is running. But the SDK is a wire — it doesn’t decide what flows through it.
A production integration means answering: How do analytics events reach Klaviyo without duplicating the dispatch logic you already have for Firebase? How do push notifications coexist with your existing firebase_messaging setup? How does the app identify a profile without creating duplicates against Klaviyo’s own Shopify sync? And how does any of this respect the user’s consent choices?
Those four questions are four workstreams. Each one has at least one non-obvious problem. Here’s what they looked like in practice, working on a Shopify-based DTC jewelry brand’s Flutter app.
Before you start: what the client must provide
Before a Klaviyo integration can begin, three things need to come from outside the development team:
1. Klaviyo public API key (site ID) — the identifier the SDK uses to associate events and profiles with your Klaviyo account. Found in Klaviyo account settings under API keys.
2. A Usercentrics data-processor entry for Klaviyo — if your app uses a CMP (and in the EU, it does), Klaviyo needs its own entry. This is a legal/compliance configuration, not an SDK task. Without it, the consent gate has nothing to check against.
3. The newsletter list ID — Klaviyo organizes subscribers into lists. The app needs the ID of the specific list to subscribe profiles to. Your marketing team creates the list; the developer gets the ID.
Workstream 1: Building a Flutter analytics layer for Klaviyo
Before touching Klaviyo, the app’s analytics needed an abstraction layer. The existing setup had ~10 call sites scattered across the codebase, each calling Firebase Analytics and an attribution service directly. No interface, no shared dispatch — every screen that tracked an event imported the Firebase service, constructed the event payload, and fired it.
Adding Klaviyo into that model would mean visiting every call site, adding a third direct call, and tripling the maintenance surface. The next provider after that — Meta, for example — would do it again.
The solution was an Adapter + Mediator pattern: a single AnalyticsDispatcher that receives events through one API and fans them out to per-provider adapters. Each adapter (Firebase, attribution, Klaviyo, and a Meta stub) implements the same interface. The dispatcher iterates over active adapters, checks consent for each, and forwards the event.
Deep dive: One trackEvent(), Four SDKs — the full architecture →
Workstream 2: Klaviyo push notifications in Flutter
Klaviyo sends push notifications through FCM on Android and APNs on iOS. The klaviyo_flutter_sdk handles push token registration — you pass the token to Klaviyo, and Klaviyo associates it with the identified profile. Straightforward in isolation.
The problem: the app already had a working push setup with firebase_messaging. On Android, three services competed for the com.google.firebase.MESSAGING_EVENT intent filter — the app’s own FirebaseMessagingService, Klaviyo’s service, and the default FCM service. Android’s service resolution dispatches to exactly one. In this case, Klaviyo’s service was winning, which meant non-Klaviyo pushes were silently dropped. The fix was routing all incoming messages through a single service and dispatching based on payload markers.
On iOS, the Notification Service Extension was required for Klaviyo’s rich push (images, action buttons). The extension intercepts the notification before display, downloads the media, and attaches it. Setting this up means a separate Xcode target, a shared app group, and careful configuration of the mutable-content flag. The tap handler also needed gating: Klaviyo pushes carry a _k marker in the payload, and the app has to distinguish those from its own deep-link pushes to route correctly.
The collision between existing firebase_messaging and the Klaviyo SDK was the most time-consuming debugging session of the project. Push notifications that silently disappear produce no error logs — the message arrives, gets dispatched to the wrong service, and vanishes. If you’re dealing with push notification complexity beyond Klaviyo, I’ve also written about designing context-aware push notification systems.
Deep dive: Klaviyo Push Notifications in Flutter — three pitfalls →
Workstream 3: Klaviyo profile identity & newsletter in Flutter
Klaviyo identifies profiles by email. The Shopify backend also syncs customer data to Klaviyo via the native Shopify-Klaviyo integration. This creates a coordination problem: the app and the backend both write to the same Klaviyo account, and they need to agree on identity.
The initial approach — passing external_id alongside email to link app-created profiles with Shopify-synced ones — backfired. Klaviyo’s profile merge logic treated external_id as a strong identifier. Instead of merging profiles, it suppressed auto-merge and created duplicates. The fix was using email-only identification from the app side and letting Klaviyo’s own merge logic handle deduplication against the Shopify sync.
Newsletter subscription added another asymmetry. Subscribing a profile to a list works through Klaviyo’s client API — the app calls Klaviyo.subscribeToList(listId) and the profile gets added. Unsubscribing, however, is not available through the client SDK. The solution was an asymmetric flow: unsubscribe triggers a custom event from the app, which fires a Klaviyo flow, which calls a webhook to remove the profile from the list. Subscribe is synchronous and direct; unsubscribe is event-driven and indirect.
Deep dive: Klaviyo Profiles & Newsletter — where the docs won’t save you →
Workstream 4: Consent management for Klaviyo with Usercentrics
Every analytics provider in the app is gated on user consent, managed through a Usercentrics CMP (Consent Management Platform). No consent, no tracking — GDPR requires it, and in practice it means the consent config blocks or unblocks each provider independently.
Klaviyo doesn’t have a runtime collection toggle. There’s no Klaviyo.setEnabled(false) — once initialized, the SDK is active. The consent gate lives in the dispatch layer: the AnalyticsDispatcher checks the user’s consent state for Klaviyo before forwarding any event to the Klaviyo adapter. If consent isn’t granted, the adapter never fires.
Firebase requires a different mechanism. Beyond the dispatch-layer gate, Firebase has auto-collection behaviors (screen views, session starts) that ignore your code-level event calls. Those need FirebaseAnalytics.setConsent() with Google Consent Mode v2 parameters — analyticsStorage, adStorage, adUserData, adPersonalization. Without this, Firebase collects data even when your dispatch layer blocks it.
The dispatcher supports both mechanisms: per-call gating for providers like Klaviyo that lack a native toggle, and SDK-level consent APIs for providers like Firebase that have one. Both check the same consent state from Usercentrics.
The Shopify checkout problem: why Flutter can’t track purchases
The app tracks three native Klaviyo metrics: openedApp, viewedProduct, and addedToCart. These fire from app code, flow through the dispatch layer, and land in Klaviyo for segmentation and flow triggers.
There’s a fourth metric you’d expect: purchase. It’s absent — and it can’t be added from the app.
The reason: checkout opens in the system browser. The user taps “Buy now,” the app launches a Shopify checkout URL in Safari or Chrome, and the purchase completes there. The app has no callback, no webhook, no way to know that the purchase happened. The browser session is outside the app’s process, and Shopify’s checkout doesn’t call back into the native app.
This means purchase-based flows in Klaviyo — post-purchase emails, win-back sequences based on order history, revenue attribution — can’t be triggered from app-side events. The data has to come from somewhere else.
Three options exist, all outside the app’s scope:
- Shopify web pixels: Shopify’s native analytics integration can forward checkout events to Klaviyo. This runs entirely in the Shopify checkout, no app involvement needed.
- Order webhooks to a backend: Shopify fires order webhooks (
orders/create) to a backend service, which calls Klaviyo’s server API to record the purchase event. - Hybrid: Combine web pixels for real-time tracking with webhooks as a fallback for reliability.
The choice depends on the client’s infrastructure. If they have a backend that already processes Shopify webhooks, the webhook path is straightforward. If they don’t, Shopify web pixels are the lower-maintenance option.
The key point: this decision needs to happen before or during the integration, not after. If the marketing team expects purchase-based flows in Klaviyo and the integration plan only covers app-side code, there’s a gap that no amount of Flutter development will close.
Hitting the same Shopify checkout gap? I’ve navigated this with Shopify-backed Flutter apps — let’s map your options in 15 minutes.
The real timeline
The project ran for about five calendar weeks, delivered across six pull requests. The phasing was deliberate: foundation first, then call-site migration, then provider integration, then QA.
| Work Package | Scope | Effort |
|---|---|---|
| Core abstraction | AnalyticsProvider interface, AnalyticsDispatcher | 2 PT |
| Firebase adapter | Wrap existing service | 2.5 PT |
| Attribution adapter | Wrap existing service + provider | 1 PT |
| Riverpod wiring + consent | Provider setup, ConsentController | 0.25 PT |
| Call-site migration | Replace in ~10 files (~15 call points) | 1 PT |
| Klaviyo SDK integration | Dependency, adapter, native setup | 2.5 PT |
| Meta stub | Placeholder ready for activation | 0.25 PT |
| QA & regression | All events, consent toggling, Klaviyo | 1 PT |
| Total | ~10 PT |
Five scoping mistakes to avoid
- Skipping the abstraction layer. Adding Klaviyo to scattered direct calls creates a migration inside a migration. The dispatch layer was about 4.5 PT upfront, but it made every subsequent provider integration a clean adapter plug-in.
- Underestimating push notification conflicts. When two services compete for the same Android intent filter, the loser doesn’t crash — it never gets called. No error logs, no exceptions. Budget time for debugging silent push failures.
- Using
external_idfor identity against a Shopify-synced account. It seems right — link app profiles to Shopify profiles by ID. In practice, it suppresses Klaviyo’s auto-merge and creates duplicates. Email-only identification is the fix. - Treating consent as a global toggle. Different providers need different consent mechanisms. A dispatch-layer gate works for Klaviyo. Firebase needs its own
setConsent()API call on top. Build for per-provider consent from the start. - Discovering the purchase tracking gap after the SDK is integrated. This is the one thing that determines whether the integration meets marketing’s expectations. Raise it in the first scoping conversation.
Conclusion
A Klaviyo integration into a production Flutter e-commerce app is four workstreams: analytics abstraction, push notifications, identity and newsletter, and consent. The SDK itself is a small part of the total effort — the architecture around it is where the weeks go. The integration shipped to production and has been running since.
The most important thing to get right isn’t code. It’s scoping the purchase tracking gap before development starts, so the marketing team’s expectations match what the app can deliver. Everything the app can track — opens, product views, cart additions — flows through cleanly. Purchases don’t, and that’s a Shopify/backend decision, not a Flutter one.
Planning a Klaviyo integration for your Flutter or mobile app? I’ve done this in production — book a casual coffee chat and I’ll walk you through what it takes for your setup. Or continue reading the series: the analytics layer · push notification pitfalls · profiles & newsletter.
See how I approach Flutter app development projects end-to-end.
Related reading
- One trackEvent(), Four SDKs: Analytics Layer for Flutter — the dispatcher architecture that made Klaviyo a clean adapter plug-in.
- Klaviyo Push Notifications in Flutter: 3 Silent Pitfalls — competing Android services, broken tap handlers, and write-once channel importance.
- Klaviyo Profiles & Newsletter: Where the Docs Won’t Save You — duplicate profiles from
external_idand the asymmetric unsubscribe flow. - Smart Push Notifications: The JITAI Framework — designing context-aware push strategies beyond basic delivery.
Frequently Asked Questions
Does Klaviyo have a Flutter SDK?
Yes. The klaviyo_flutter_sdk package wraps the native Klaviyo iOS and Android SDKs and covers profile management, event tracking, and push notifications. It handles push token registration via FCM on Android and APNs on iOS.
How long does a Klaviyo integration into a Flutter app take?
For a production e-commerce app, the realistic scope is several weeks, not days. In the project this article is based on, the work spanned about five weeks and six pull requests across a unified analytics layer, the Klaviyo SDK integration, push notifications, newsletter and identity handling, and consent management. The analytics abstraction alone — building the dispatch layer and migrating existing call sites — accounted for roughly half the total effort.
What does the client need to provide before a Klaviyo integration starts?
Three things: the Klaviyo public API key (site ID), a Klaviyo entry in the consent management platform (e.g. Usercentrics), and the ID of the newsletter list the app should subscribe profiles to. All three require action from someone outside the development team — account settings, legal/compliance configuration, and marketing setup respectively.
Can the app track purchases in Klaviyo if checkout runs in the browser?
Not from the app alone. If checkout opens in an external browser, the app loses context and cannot emit purchase events. The app can track openedApp, viewedProduct, and addedToCart, but purchase tracking requires a Shopify-side or server-side solution. Options include Shopify web pixels, order webhooks to a backend, or a hybrid of both.