# How to Integrate Bolt.new with Klaviyo

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Add Klaviyo to your Bolt.new app by including the Klaviyo JavaScript tracking snippet for automatic event capture (works in the WebContainer preview), and building API routes for server-side operations like creating campaigns and managing lists. Get your Public API Key and Private API Key from Klaviyo Account → Settings → API Keys. The tracking snippet fires analytics events directly from the browser — no API route needed for basic tracking.

## Add E-commerce Email Marketing to Your Bolt.new App with Klaviyo

Klaviyo is the email marketing platform of choice for e-commerce businesses — it tracks individual customer behavior, segments audiences based on purchase history, and triggers automated email flows when specific events occur. Unlike general-purpose email tools, Klaviyo's entire design revolves around connecting marketing actions to revenue. When a user views a product, abandons a cart, or makes a purchase, Klaviyo captures that event, adds it to the customer's profile, and can trigger targeted follow-up emails automatically.

For Bolt.new apps, Klaviyo's JavaScript tracking snippet is the fastest integration path. Add the snippet to your HTML head, identify users with their email address, and start firing custom events using window.klaviyo.track(). This client-side approach works immediately in Bolt's WebContainer preview because it only makes outbound HTTP calls to Klaviyo's servers — no incoming connections required. The Public API Key (safe to expose in client-side code) is all you need for tracking.

For server-side operations — creating subscribers, managing list membership, sending transactional emails, or fetching analytics data — Klaviyo provides a comprehensive REST API (v3) that requires your Private API Key. These operations run through server-side API routes in your Bolt app, keeping the private key out of client-side code. Klaviyo's API is well-documented and follows standard REST conventions, making it straightforward to implement with the patterns Bolt generates.

## Before you start

- A Klaviyo account (free tier available at klaviyo.com for up to 500 contacts and 500 email sends/month)
- Your Klaviyo Public API Key from Account → Settings → API Keys (safe for client-side use)
- Your Klaviyo Private API Key from the same page (server-side only — never expose in client code)
- At least one Klaviyo List created (Account → Lists & Segments → Create List) for subscriber management
- A Bolt.new project (Vite or Next.js)

## Step-by-step guide

### 1. Add the Klaviyo JavaScript Tracking Snippet

Klaviyo's JavaScript tracking snippet is the fastest way to start capturing behavioral data — it loads asynchronously, identifies users by email, and tracks custom events with a simple push-based API. The snippet works immediately in Bolt's WebContainer preview because it only makes outbound calls to Klaviyo's servers, not the other way around. Start by locating your Public API Key: in your Klaviyo account, click your account name in the bottom-left → Settings → API Keys. Your Public API Key is shown at the top — it starts with a 6-character alphanumeric string. This key is safe to include in client-side code and in your .env file with the VITE_ prefix for Vite projects. The tracking snippet should be added to your app's main HTML file (index.html for Vite projects, or in the layout file for Next.js). Prompt Bolt to add the snippet. Once the snippet loads, the global window._learnq array is available. To identify a user (associate their actions with an email address), call: window._learnq.push(['identify', { '$email': 'user@example.com' }]). Until a user is identified, all tracked events are associated with an anonymous profile in Klaviyo that gets merged when the email is collected later (for example, when they sign up or log in).

```
<!-- index.html — add to <head> -->
<script>
  !(function () {
    if (!window.klaviyo) {
      window._learnq = window._learnq || [];
      window._learnq.push(['account', import.meta.env.VITE_KLAVIYO_PUBLIC_KEY]);
      var b = document.createElement('script');
      b.type = 'text/javascript';
      b.async = !0;
      b.src = 'https://static.klaviyo.com/onsite/js/klaviyo.js?company_id=' + import.meta.env.VITE_KLAVIYO_PUBLIC_KEY;
      var a = document.getElementsByTagName('script')[0];
      a.parentNode.insertBefore(b, a);
    }
  })();
</script>

// lib/klaviyo.ts — typed helper
declare global {
  interface Window {
    _learnq: unknown[];
  }
}

export function identifyUser(email: string, properties?: Record<string, unknown>) {
  window._learnq = window._learnq || [];
  window._learnq.push(['identify', { '$email': email, ...properties }]);
}

export function trackEvent(eventName: string, properties?: Record<string, unknown>) {
  window._learnq = window._learnq || [];
  window._learnq.push(['track', eventName, properties || {}]);
}
```

**Expected result:** The Klaviyo snippet loads in your app. Opening Klaviyo → Activity → Live View shows page view events from your Bolt preview sessions.

### 2. Build an Email Signup Form with List Subscription API

For collecting email addresses and adding subscribers to Klaviyo lists, you'll use Klaviyo's REST API v3 with your Private API Key. The Private API Key must never be exposed in client-side code — always call it from a server-side API route. In a Vite project, you can use a Vite server proxy to route API calls through a development server, or use Next.js API routes for clean separation. The Klaviyo v3 API uses a specific JSON:API structure for profile creation: you POST to /api/profiles/ with a data object containing type and attributes. After creating or updating the profile, subscribe it to a list with a separate call to /api/lists/{list_id}/relationships/profiles/. The list ID is visible in your Klaviyo account under Lists & Segments — click your list and find the list ID in the URL bar (a 6-character string like AbCdEf). Store the list ID in your .env file. Klaviyo's profile creation is idempotent for existing emails — if the email already exists as a profile, it updates the profile instead of creating a duplicate. The API returns a 201 status for new profiles and 200 for updates. Always handle both response codes in your API route. For form submission security, add validation for email format and implement a honeypot field to reduce bot submissions. Rate limits on Klaviyo's v3 API are generous (500 requests per second), so you won't hit them in normal Bolt app usage.

```
// app/api/klaviyo/subscribe/route.ts
import { NextRequest, NextResponse } from 'next/server';

const KLAVIYO_BASE = 'https://a.klaviyo.com/api';
const HEADERS = {
  'Authorization': `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,
  'revision': '2024-02-15',
  'Content-Type': 'application/json',
};

export async function POST(request: NextRequest) {
  const { email } = await request.json();

  if (!email || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
    return NextResponse.json({ error: 'Invalid email address' }, { status: 400 });
  }

  // Step 1: Create or update profile
  const profileRes = await fetch(`${KLAVIYO_BASE}/profiles/`, {
    method: 'POST',
    headers: HEADERS,
    body: JSON.stringify({
      data: {
        type: 'profile',
        attributes: { email },
      },
    }),
  });

  const profileData = await profileRes.json();
  const profileId = profileData.data?.id;

  if (!profileId) {
    return NextResponse.json({ error: 'Failed to create profile' }, { status: 500 });
  }

  // Step 2: Subscribe to list
  await fetch(`${KLAVIYO_BASE}/lists/${process.env.KLAVIYO_LIST_ID}/relationships/profiles/`, {
    method: 'POST',
    headers: HEADERS,
    body: JSON.stringify({
      data: [{ type: 'profile', id: profileId }],
    }),
  });

  return NextResponse.json({ success: true });
}
```

**Expected result:** Submitting the signup form creates a new profile in Klaviyo and adds them to your list. You can verify in Klaviyo → Lists & Segments → your list.

### 3. Track Custom E-commerce Events

Klaviyo's power comes from tracking specific user behaviors that trigger automated email flows — viewing a product, adding to cart, starting checkout, and completing a purchase. These events build the customer profiles that power segmentation and automation. Once you have the tracking snippet installed (Step 1), firing custom events is a one-line call anywhere in your React components. For e-commerce events, Klaviyo has defined schemas for standard events: 'Viewed Product', 'Added to Cart', 'Started Checkout', and 'Placed Order'. Using these exact event names unlocks Klaviyo's pre-built automation templates (like abandoned cart flows) without any additional configuration. Each event takes a properties object — for products, include ProductName, ProductID, Price, ImageURL, and URL at minimum. For orders, include OrderId, ItemNames, ItemCount, and Value. The properties object can contain any additional custom fields you want to filter on when creating segments. Call identifyUser() before or alongside trackEvent() to ensure events are associated with a known email address. For logged-in users, identify them on app load; for anonymous visitors, the events are stored as anonymous and merged when the user provides their email. Important: Klaviyo's JavaScript API is client-side only and works in Bolt's WebContainer preview without any deployment or server-side setup. You can see events flowing in Klaviyo → Activity → Live View within seconds of firing them from your preview.

```
// hooks/useProductTracking.ts
import { useEffect } from 'react';
import { trackEvent } from '@/lib/klaviyo';

interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
  imageUrl: string;
}

export function useProductTracking(product: Product | null) {
  useEffect(() => {
    if (!product) return;
    trackEvent('Viewed Product', {
      ProductName: product.name,
      ProductID: product.id,
      Categories: [product.category],
      ImageURL: product.imageUrl,
      URL: window.location.href,
      Price: product.price,
    });
  }, [product?.id]);
}

export function trackAddToCart(product: Product, quantity: number, cartItems: Product[]) {
  trackEvent('Added to Cart', {
    $value: product.price * quantity,
    AddedItemProductName: product.name,
    AddedItemProductID: product.id,
    AddedItemPrice: product.price,
    AddedItemQuantity: quantity,
    ItemNames: cartItems.map((i) => i.name),
    CheckoutURL: `${window.location.origin}/checkout`,
  });
}

export function trackPurchase(orderId: string, items: Product[], total: number) {
  trackEvent('Placed Order', {
    $value: total,
    OrderId: orderId,
    ItemNames: items.map((i) => i.name),
    Categories: [...new Set(items.map((i) => i.category))],
  });
}
```

**Expected result:** Navigating through your Bolt app triggers Klaviyo events. Check Klaviyo → Activity → Live View to see events appearing in real-time with their properties.

### 4. Deploy and Set Environment Variables for Server-Side Operations

The Klaviyo tracking snippet (Public API Key) works in Bolt's WebContainer preview, but server-side API routes using the Private API Key need proper environment variable configuration both locally and in production. In Bolt's project, your .env file holds both the public and private keys: VITE_KLAVIYO_PUBLIC_KEY for the tracking snippet (VITE_ prefix makes it available in client-side Vite code), KLAVIYO_PRIVATE_KEY for server-side API routes (no VITE_ prefix keeps it server-only), and KLAVIYO_LIST_ID for your subscriber list ID. When deploying to Netlify, navigate to Site Configuration → Environment Variables and add KLAVIYO_PRIVATE_KEY and KLAVIYO_LIST_ID. Also add NEXT_PUBLIC_KLAVIYO_PUBLIC_KEY (or VITE_KLAVIYO_PUBLIC_KEY) for the client-side snippet. For Bolt Cloud deployment, go to the Secrets panel in your project settings and add the same variables. After deployment, your subscriber API routes are accessible from the public internet and email signups will flow directly into Klaviyo. Klaviyo's webhook system (for receiving notifications when email events occur, like unsubscribes or bounces) also becomes available post-deployment — Klaviyo webhooks send events to your app when subscribers interact with emails. Register your webhook URL in Klaviyo → Account → Settings → Webhooks. During development in Bolt's WebContainer, incoming Klaviyo webhooks cannot be received because the preview URL is dynamic and unreachable from Klaviyo's servers — deploy first, then test webhooks on your production URL.

```
// app/api/klaviyo/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  // Klaviyo sends a signing key in headers for verification
  // See Klaviyo docs for HMAC-based webhook verification
  const payload = await request.json();

  const { type, data } = payload;

  switch (type) {
    case 'profile.unsubscribed_from_list':
      console.log('Unsubscribe event:', data?.attributes?.email);
      // Remove from your database, update user preferences, etc.
      break;
    case 'profile.subscribed_to_list':
      console.log('New subscriber:', data?.attributes?.email);
      break;
    default:
      console.log('Unhandled Klaviyo event:', type);
  }

  return NextResponse.json({ received: true });
}
```

**Expected result:** The deployed app accepts email signups that appear in your Klaviyo lists. Klaviyo webhooks deliver unsubscribe and subscription events to your deployed webhook endpoint.

## Best practices

- Use the Klaviyo JavaScript snippet for all client-side event tracking — it's simpler than API route calls and fires events immediately without server round-trips
- Never put your Klaviyo Private API Key in client-side code or the VITE_ prefix — it belongs in server-side API routes only
- Use Klaviyo's standard event names ('Viewed Product', 'Added to Cart', 'Placed Order') to unlock pre-built automation flow templates without additional configuration
- Always include the required 'revision' header in all Klaviyo v3 API requests — missing it causes 400 errors
- Identify users with window._learnq.push(['identify', ...]) as early as possible in your app flow so events are associated with email addresses, not anonymous profiles
- Set up Klaviyo webhooks after deploying to receive real-time notifications about unsubscribes and bounces — the WebContainer preview cannot receive incoming webhook requests
- Cache Klaviyo analytics responses in your dashboard API routes — campaign data rarely changes minute-to-minute and caching reduces API calls

## Use cases

### Email Signup Form with List Subscription

Add a newsletter signup form to your Bolt app that collects email addresses and subscribes visitors to a specific Klaviyo list. On submission, an API route calls Klaviyo's profiles API to create or update the subscriber profile and add them to your newsletter list — ready to receive your next campaign.

Prompt example:

```
Create an email signup form with an Email input field and a Subscribe button. On submit, call POST /api/klaviyo/subscribe with the email address. The API route should call Klaviyo's REST API v3 to create a profile (POST to https://a.klaviyo.com/api/profiles/) and then subscribe it to my list using the list ID from KLAVIYO_LIST_ID env var. Use the KLAVIYO_PRIVATE_KEY env var for the Authorization header. Show a success message after subscribing. Add a honeypot field to prevent bot submissions.
```

### E-commerce Behavioral Event Tracking

Track product interactions in a Bolt e-commerce app — viewed products, cart additions, and completed purchases. Klaviyo uses these events to build customer segments and trigger automated flows like abandoned cart emails. The JavaScript tracking snippet fires events directly from the browser with no server-side code.

Prompt example:

```
Add Klaviyo event tracking to my e-commerce Bolt app. First, add the Klaviyo tracking snippet to the HTML head using my Public API Key from VITE_KLAVIYO_PUBLIC_KEY. Then, on the product page, call window._learnq.push(['track', 'Viewed Product', { ProductName: product.name, ProductID: product.id, Price: product.price, ImageURL: product.imageUrl }]) when the page loads. On the cart page, fire an 'Added to Cart' event when items are added. On checkout completion, fire a 'Placed Order' event with the order total and item list.
```

### Campaign Analytics Dashboard

Build an internal analytics dashboard in Bolt that pulls Klaviyo campaign performance data — open rates, click rates, revenue per recipient, and unsubscribe rates. The dashboard gives your marketing team a quick overview without logging into Klaviyo's full interface.

Prompt example:

```
Build a Klaviyo analytics dashboard in my Bolt app. Create an API route GET /api/klaviyo/campaigns that fetches the last 20 campaigns from Klaviyo's API: GET https://a.klaviyo.com/api/campaigns/?filter=equals(messages.channel,'email')&sort=-created_at&page[size]=20 using the KLAVIYO_PRIVATE_KEY. For each campaign, also fetch its metrics. Display the campaigns in a table with columns: Name, Status, Send Date, Recipients, Open Rate, Click Rate. Use Recharts to show a bar chart of open rates across the last 10 campaigns.
```

## Troubleshooting

### Klaviyo tracking snippet fires but no events appear in Klaviyo Live View

Cause: The Public API Key is incorrect, the snippet is loading before the DOM is ready, or an ad blocker is preventing the Klaviyo script from loading in the browser.

Solution: Open browser DevTools → Network tab and filter for 'klaviyo'. Verify the script request succeeds (200 status). Check the URL includes your correct company ID (Public API Key). Confirm your Public API Key in Klaviyo → Account → Settings → API Keys. Disable browser ad blockers for testing — Klaviyo's tracking domain is sometimes blocked.

### POST /api/klaviyo/subscribe returns 400 error with 'Invalid revision'

Cause: The required 'revision' header is missing from the API request. Klaviyo's v3 API requires this header on all requests.

Solution: Add the 'revision' header to all API v3 requests. Use '2024-02-15' or the latest revision from Klaviyo's changelog.

```
// All Klaviyo v3 API requests need this header
const headers = {
  'Authorization': `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,
  'revision': '2024-02-15',
  'Content-Type': 'application/json',
};
```

### Profile is created but not added to the Klaviyo list

Cause: The list subscription call is made before the profile creation fully resolves, or the list ID is incorrect.

Solution: Ensure you await the profile creation response and extract the profile ID before making the list subscription call. Verify the KLAVIYO_LIST_ID matches the list ID shown in Klaviyo → Lists & Segments → your list URL (6-character alphanumeric ID).

```
// Correct: await profile creation, then use returned ID for list subscription
const profileData = await profileRes.json();
const profileId = profileData.data?.id;
if (!profileId) throw new Error('Profile creation failed');
// Now use profileId in the list subscription call
```

## Frequently asked questions

### Can I use Klaviyo in Bolt.new without deploying?

Yes — the Klaviyo JavaScript tracking snippet works immediately in Bolt's WebContainer preview. You can track page views, product views, and custom events during development. Server-side API routes for list subscriptions also work in the preview since they make outbound calls only. However, receiving Klaviyo webhooks (for unsubscribe notifications and email event data) requires a deployed app with a public URL.

### What is the difference between Klaviyo's Public API Key and Private API Key?

The Public API Key (also called the site ID or company ID) is safe to include in client-side code and the browser-loaded tracking snippet. It only allows event tracking and profile identification. The Private API Key has full API access — creating profiles, managing lists, accessing analytics, and sending emails. It must be kept in server-side API routes only and never exposed in client-side code or committed to a public repository.

### Does the Klaviyo free plan allow API access?

Yes — Klaviyo's free plan includes full REST API access for up to 500 contacts and 500 email sends per month. This is sufficient for development and small apps. The API key is available immediately after signup with no approval process. Rate limits are generous at 500 requests per second on the v3 API.

### How do I trigger an automated email flow from my Bolt app?

Klaviyo flows are triggered by events, not direct API calls. When your app fires a tracking event (e.g., 'Added to Cart'), Klaviyo evaluates it against all active flows with that trigger. To create an abandoned cart flow: set up the flow in Klaviyo with trigger 'Added to Cart', add a time delay (e.g., 4 hours), then add an email action. The flow fires automatically when anyone tracked in your Bolt app adds to cart and doesn't complete a purchase within that window.

### Can Bolt.new receive Klaviyo webhook events during development?

No — during development in Bolt's WebContainer, the preview URL is dynamic and cannot be registered as a stable webhook endpoint in Klaviyo. Deploy your app to Netlify or Bolt Cloud first to get a stable public URL, then register it in Klaviyo → Account → Settings → Webhooks. This is a fundamental limitation of WebContainers — they run in the browser and cannot receive incoming HTTP connections from external services.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/klaviyo
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/klaviyo
