To integrate Replit with Klaviyo, store your Klaviyo Private API key in Replit Secrets (lock icon π) and call the Klaviyo REST API v2025 from your server-side code using Node.js or Python. Klaviyo is built for e-commerce email and SMS automation β from Replit you can sync customer profiles, track purchase events, trigger flows, and manage campaigns. Use Autoscale deployment to receive Klaviyo webhook events for real-time automation.
E-Commerce Email and SMS Automation from Replit with Klaviyo
Klaviyo powers email and SMS marketing for over 130,000 e-commerce brands, with tight native integrations for Shopify and WooCommerce. Its API-first architecture means you can replicate that same power for custom storefronts, headless commerce setups, or any application that needs to send purchase confirmations, abandoned cart reminders, post-purchase win-back sequences, or triggered SMS alerts. From Replit, you can build the backend bridge that feeds Klaviyo with the customer data and behavioral signals it needs to run sophisticated automation flows.
The Klaviyo API v2025 uses a JSON:API-style request format with a required Revision header specifying the API version. This versioning approach means your integration stays stable even as Klaviyo adds new capabilities β you pin to a specific revision and upgrade on your own schedule. Authentication uses Bearer token with your Private API key (never the Public API key, which is for browser-side tracking only and should not be used server-side).
The most valuable Klaviyo API patterns for Replit are profile management and event tracking. Profile management lets you create customer records, update preferences, and manage list subscriptions programmatically β essential for custom checkout flows or imported contact lists. Event tracking lets you send behavioral data (product views, add-to-cart, purchases, refunds) that Klaviyo uses to trigger automated flows and segment audiences. Combined, these two patterns let you build a complete e-commerce marketing stack on top of your own custom application infrastructure.
Integration method
Klaviyo integrates with Replit through Klaviyo's REST API v2025 using a Private API key for server-side requests. Your Replit backend authenticates with the Revision header and Bearer token to create and update profiles, track custom events, subscribe users to email lists, and trigger marketing flows. Klaviyo also supports webhook subscriptions that deliver real-time events (form submissions, unsubscribes, email bounces) to your deployed Replit endpoint.
Prerequisites
- A Replit account with a Node.js or Python Repl ready
- A Klaviyo account β free plans include up to 500 contacts and 150 email sends per month
- Your Klaviyo Private API key from Account Settings β API Keys (use Private, not Public)
- Your Klaviyo List ID for the lists you want to manage (found in Lists & Segments in Klaviyo)
Step-by-step guide
Get Your Klaviyo API Key and Store It in Replit Secrets
Get Your Klaviyo API Key and Store It in Replit Secrets
Klaviyo has two types of API keys with very different purposes. The Public API key (also called the Site ID or Company ID) is a short alphanumeric string meant for client-side JavaScript tracking β it is safe to expose in browser code. The Private API key is for server-side API calls β it must never appear in frontend code or be committed to version control. You need the Private key for all Replit integrations. To get your Private API key: log into Klaviyo β click your account name (bottom left) β Settings β API Keys β Create Private API Key. Choose a descriptive name (e.g., 'Replit Integration') and select the minimum required scopes for your use case. For profile management and event tracking, you need: Read/Write access for Profiles, Events, and Lists. For webhook management, also add Webhooks. Klaviyo API keys are scoped β you can create multiple keys with different permission levels. Create a key with only the scopes your Replit app needs, following the principle of least privilege. Open your Replit project and click the lock icon (π) in the left sidebar. Add these secrets: KLAVIYO_PRIVATE_KEY: your Klaviyo Private API key. KLAVIYO_LIST_ID: the Klaviyo List ID for your primary subscriber list (optional, used in subscription workflows). Klaviy's API now requires a Revision header on all requests specifying the API version (currently '2024-10-15' for stable requests β check Klaviyo's changelog for the latest). This is included in the code examples below.
1// check-klaviyo-secrets.js2const required = ['KLAVIYO_PRIVATE_KEY'];3for (const key of required) {4 if (!process.env[key]) {5 throw new Error(`Missing secret: ${key}. Add it in Replit Secrets (lock icon π).`);6 }7}8// Validate key format β Klaviyo private keys start with pk_9if (!process.env.KLAVIYO_PRIVATE_KEY.startsWith('pk_')) {10 console.warn('Warning: Klaviyo private keys typically start with pk_ β verify you are using the Private key, not the Public key.');11}12console.log('Klaviyo secrets configured.');13console.log('Key prefix:', process.env.KLAVIYO_PRIVATE_KEY.slice(0, 8) + '...');Pro tip: Klaviyo Private API keys start with 'pk_' while the Public API key (Site ID) is a short string without that prefix. If your key does not start with 'pk_', you likely grabbed the Public key β go back to Settings β API Keys and look for the Private keys section.
Expected result: KLAVIYO_PRIVATE_KEY is set in Replit Secrets. The verification script confirms the key format and prints without errors.
Create and Update Customer Profiles with Node.js
Create and Update Customer Profiles with Node.js
Klaviyo's Profiles API lets you create customer records with contact information, custom properties, and predictive analytics data. The v2025 API uses a JSON:API-style request body where profile data is nested under a 'data' key with a 'type' of 'profile'. All requests require two headers: 'Authorization: Klaviyo-API-Key {key}' and 'revision: {api-version}'. Profile identification works by email β if you create a profile with an email that already exists, Klaviyo merges the data instead of creating a duplicate. This idempotent behavior is useful for checkout flows where the same customer might trigger the API multiple times. Custom properties (arbitrary key-value data about the customer) are stored in the 'properties' object and can be any data relevant to your business β subscription tier, loyalty points, last purchase date, product preferences. These properties can be used for segmentation and flow triggers in Klaviyo. The upsert endpoint (POST /api/profile-import/) handles both create and update in a single call β it is the recommended approach for syncing profiles from external systems since it handles the 'does this profile already exist?' question automatically.
1// klaviyo-profiles.js β Manage Klaviyo profiles from Replit2const KLAVIYO_KEY = process.env.KLAVIYO_PRIVATE_KEY;3const REVISION = '2024-10-15'; // Klaviyo API revision date4const BASE_URL = 'https://a.klaviyo.com/api';56async function klaviyoRequest(method, endpoint, body = null) {7 const options = {8 method,9 headers: {10 'Authorization': `Klaviyo-API-Key ${KLAVIYO_KEY}`,11 'revision': REVISION,12 'Content-Type': 'application/json',13 'Accept': 'application/json'14 }15 };16 if (body) options.body = JSON.stringify(body);1718 const res = await fetch(`${BASE_URL}${endpoint}`, options);19 if (!res.ok) {20 const errText = await res.text();21 throw new Error(`Klaviyo API ${res.status}: ${errText}`);22 }23 // 204 No Content responses have no body24 if (res.status === 204) return null;25 return res.json();26}2728// Create or update (upsert) a customer profile29async function upsertProfile(email, firstName, lastName, phone, customProps = {}) {30 const payload = {31 data: {32 type: 'profile',33 attributes: {34 email,35 first_name: firstName,36 last_name: lastName,37 phone_number: phone, // E.164 format: +1555123456738 properties: customProps39 }40 }41 };4243 // POST /profile-import upserts by email (create or update)44 const result = await klaviyoRequest('POST', '/profile-import/', payload);45 const profileId = result?.data?.id;46 console.log(`Profile upserted: ${profileId} (${email})`);47 return profileId;48}4950// Get a profile by email51async function getProfileByEmail(email) {52 const params = new URLSearchParams({ 'filter': `equals(email,"${email}")` });53 const result = await klaviyoRequest('GET', `/profiles/?${params}`);54 return result?.data?.[0] || null;55}5657// Update specific profile properties58async function updateProfile(profileId, properties) {59 const payload = {60 data: {61 type: 'profile',62 id: profileId,63 attributes: { properties }64 }65 };66 return klaviyoRequest('PATCH', `/profiles/${profileId}/`, payload);67}6869// Example70(async () => {71 try {72 const profileId = await upsertProfile(73 'customer@example.com',74 'Jane', 'Smith', '+15551234567',75 { plan_type: 'premium', signup_source: 'checkout' }76 );77 console.log('Profile ID:', profileId);78 } catch (err) {79 console.error('Error:', err.message);80 }81})();8283module.exports = { upsertProfile, getProfileByEmail, updateProfile, klaviyoRequest };Pro tip: Klaviyo's profile upsert (POST /profile-import/) merges properties on update β existing custom properties are preserved unless you explicitly overwrite them. Sending a partial update only changes the fields you include.
Expected result: Running the script creates or updates a Klaviyo profile. The profile appears in Klaviyo's Profiles section with the email, name, and custom properties.
Track E-Commerce Events and Subscribe to Lists
Track E-Commerce Events and Subscribe to Lists
Klaviyo's power comes from event-triggered automation β the Events API lets you send behavioral signals that trigger flows, update segments, and feed predictive analytics. For e-commerce, the key events are 'Placed Order', 'Ordered Product' (one event per line item), 'Started Checkout', 'Viewed Product', and 'Added to Cart'. Klaviyo recognizes these as standard e-commerce events and connects them to built-in flow templates. Custom events are also supported for non-standard actions. Give them clear names like 'Completed Onboarding', 'Upgraded Plan', or 'Downloaded Report' β these become available as flow triggers and segment filters in Klaviyo. List subscription is handled through the Lists API's subscribe endpoint. To add a profile to a list, call POST /api/lists/{list_id}/relationships/profiles/ with the profile IDs. For email subscriptions specifically, use the Subscriptions API to explicitly set the subscription channel and consent status. For Python, the structure is identical β use the requests library with the same headers and JSON body format.
1# klaviyo_events.py β Track events and manage subscriptions in Klaviyo2import os3import requests4from datetime import datetime, timezone56KLAVIYO_KEY = os.environ['KLAVIYO_PRIVATE_KEY']7KLAVIYO_LIST_ID = os.environ.get('KLAVIYO_LIST_ID', '')8REVISION = '2024-10-15'9BASE_URL = 'https://a.klaviyo.com/api'1011HEADERS = {12 'Authorization': f'Klaviyo-API-Key {KLAVIYO_KEY}',13 'revision': REVISION,14 'Content-Type': 'application/json',15 'Accept': 'application/json'16}1718def track_event(event_name: str, email: str, properties: dict) -> bool:19 """20 Track a custom event for a profile identified by email.21 Use standard names like 'Placed Order', 'Started Checkout' for e-commerce.22 """23 payload = {24 'data': {25 'type': 'event',26 'attributes': {27 'time': datetime.now(timezone.utc).isoformat(),28 'value': properties.get('value', 0),29 'metric': {30 'data': {31 'type': 'metric',32 'attributes': {'name': event_name}33 }34 },35 'profile': {36 'data': {37 'type': 'profile',38 'attributes': {'email': email}39 }40 },41 'properties': properties42 }43 }44 }45 response = requests.post(f'{BASE_URL}/events/', headers=HEADERS, json=payload)46 response.raise_for_status()47 print(f'Event tracked: {event_name} for {email}')48 return True4950def track_order(email: str, order_id: str, total: float, items: list):51 """Track a 'Placed Order' event with line items."""52 return track_event('Placed Order', email, {53 'order_id': order_id,54 'value': total,55 '$value': total, # Klaviyo uses $value for revenue tracking56 'items': items, # List of {product_id, name, price, quantity}57 'item_count': len(items)58 })5960def subscribe_to_list(profile_id: str, list_id: str = None) -> bool:61 """Subscribe a profile to a Klaviyo email list."""62 lid = list_id or KLAVIYO_LIST_ID63 if not lid:64 raise ValueError('No list ID provided. Set KLAVIYO_LIST_ID in Replit Secrets.')6566 payload = {67 'data': [{'type': 'profile', 'id': profile_id}]68 }69 response = requests.post(70 f'{BASE_URL}/lists/{lid}/relationships/profiles/',71 headers=HEADERS, json=payload72 )73 response.raise_for_status()74 print(f'Profile {profile_id} subscribed to list {lid}')75 return True7677if __name__ == '__main__':78 # Track a test order event79 track_order(80 email='customer@example.com',81 order_id='ORD-12345',82 total=99.99,83 items=[84 {'product_id': 'PROD-001', 'name': 'Widget Pro', 'price': 49.99, 'quantity': 2}85 ]86 )Pro tip: Use the $value property (with dollar sign) in event properties when tracking revenue β Klaviyo specifically looks for $value to populate its revenue analytics dashboards and calculate customer lifetime value metrics.
Expected result: Running the script tracks a 'Placed Order' event visible in Klaviyo's event feed for the customer profile. The event appears under the profile's Activity timeline in Klaviyo.
Receive Klaviyo Webhook Events on Replit
Receive Klaviyo Webhook Events on Replit
Klaviyo can deliver real-time webhook events to your Replit server when important things happen in your account β email bounces, unsubscribes, SMS opt-outs, profile updates, and form submissions. This is useful for keeping your application database in sync with Klaviyo's subscription state. To set up webhooks: in Klaviyo, go to Account β Settings β Webhooks β Create Webhook. Enter your deployed Replit URL (https://yourapp.replit.app/klaviyo/webhook) and select the event types you want to receive. Klaviyo sends POST requests with JSON payloads to your endpoint. Klaviyo signs webhook requests with an HMAC-SHA256 signature in the X-Klaviyo-Signature header. Verify this signature before processing the event to prevent spoofed requests. The signature is computed over the request body using your webhook signing secret as the key. Your webhook endpoint must return 200 within a few seconds. If it takes longer or returns an error, Klaviyo retries the delivery. Use Autoscale or Reserved VM deployment to keep the endpoint consistently available. Klaviyo webhook payloads follow a consistent JSON structure with event type and data β parse the type field to route different events to different handlers in your application.
1// klaviyo-webhook.js β Receive and process Klaviyo webhook events2const express = require('express');3const crypto = require('crypto');4const app = express();56// Use raw body for signature verification7app.use('/klaviyo/webhook', express.raw({ type: 'application/json' }));8app.use(express.json());910const WEBHOOK_SECRET = process.env.KLAVIYO_WEBHOOK_SECRET;1112function verifyKlaviyoSignature(rawBody, signatureHeader) {13 if (!WEBHOOK_SECRET) return true; // Skip verification if no secret set (dev only)14 const expected = crypto15 .createHmac('sha256', WEBHOOK_SECRET)16 .update(rawBody)17 .digest('base64');18 return crypto.timingSafeEqual(19 Buffer.from(expected),20 Buffer.from(signatureHeader || '')21 );22}2324app.post('/klaviyo/webhook', (req, res) => {25 const signature = req.headers['x-klaviyo-signature'];26 const rawBody = req.body; // raw Buffer due to express.raw()2728 if (!verifyKlaviyoSignature(rawBody, signature)) {29 console.error('Invalid Klaviyo webhook signature');30 return res.status(401).json({ error: 'Invalid signature' });31 }3233 let events;34 try {35 events = JSON.parse(rawBody.toString());36 if (!Array.isArray(events)) events = [events];37 } catch (err) {38 return res.status(400).json({ error: 'Invalid JSON' });39 }4041 for (const event of events) {42 const type = event.type;43 const attributes = event.attributes || {};4445 console.log('Klaviyo webhook event:', type);4647 if (type === 'profile.unsubscribed') {48 const email = attributes.email;49 console.log(`Unsubscribe received for: ${email}`);50 // TODO: update subscription status in your database51 } else if (type === 'email.bounced') {52 const email = attributes.email;53 console.log(`Email bounced for: ${email}`);54 // TODO: mark email as bounced in your database55 } else if (type === 'sms.unsubscribed') {56 const phone = attributes.phone_number;57 console.log(`SMS opt-out from: ${phone}`);58 } else {59 console.log('Event data:', JSON.stringify(attributes, null, 2));60 }61 }6263 res.status(200).json({ received: true });64});6566app.listen(3000, '0.0.0.0', () => {67 console.log('Klaviyo webhook server running on port 3000');68 console.log('Set webhook URL in Klaviyo: https://yourapp.replit.app/klaviyo/webhook');69});Pro tip: Add KLAVIYO_WEBHOOK_SECRET to Replit Secrets after creating the webhook in Klaviyo. Klaviyo shows you the signing secret when you create the webhook β copy it immediately as it is not shown again.
Expected result: The webhook server starts on port 3000. After deploying and configuring the webhook URL in Klaviyo settings, events (unsubscribes, bounces) trigger the endpoint and appear in deployment logs.
Common use cases
Custom Checkout Event Tracking
For headless commerce or custom checkout implementations not using Shopify or WooCommerce, send purchase events and abandoned cart signals directly to Klaviyo via the Events API. Klaviyo uses these events to trigger post-purchase email sequences, abandoned cart recovery flows, and product review requests β the same automation available in native Shopify integrations.
Build a checkout webhook receiver that fires when a customer completes a purchase, creates or updates their Klaviyo profile with order details, tracks a 'Placed Order' event with the product list and order value, and adds them to a post-purchase email sequence list.
Copy this prompt to try it in Replit
Customer Profile Sync and List Management
Synchronize customer data from your database or CRM into Klaviyo profiles, ensuring your email segments stay current. When customers update preferences (opt in, opt out, change email), update both your database and Klaviyo simultaneously from your Replit backend to maintain consistency across systems.
Create an API endpoint that accepts customer data from a registration form, creates a Klaviyo profile with full contact details and custom properties (plan_type, signup_source), subscribes them to a welcome email list, and returns the Klaviyo profile ID for storage in your database.
Copy this prompt to try it in Replit
Webhook-Driven Marketing Automation Triggers
Listen for Klaviyo webhook events (unsubscribes, email bounces, SMS opt-outs) to update your application database in real time. When Klaviyo detects a bounce or unsubscribe, your Replit endpoint receives the event and marks the customer as unsubscribed in your system, preventing future attempts to send to that address.
Build a Klaviyo webhook handler that receives unsubscribe and bounce events, updates the customer's subscription status in your database, and logs the event with timestamp for compliance reporting.
Copy this prompt to try it in Replit
Troubleshooting
401 Unauthorized on all Klaviyo API requests
Cause: The KLAVIYO_PRIVATE_KEY in Replit Secrets is incorrect, or you are using the Public API key (Site ID) instead of the Private API key. The Authorization header format must be exactly 'Klaviyo-API-Key {key}' β not 'Bearer {key}' or just '{key}'.
Solution: In Klaviyo Settings β API Keys, confirm you are copying the Private API key (starts with pk_). Update KLAVIYO_PRIVATE_KEY in Replit Secrets. Check that your code uses the exact header format: Authorization: `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`.
1// Correct Authorization header format for Klaviyo API v2024+2const headers = {3 'Authorization': `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,4 'revision': '2024-10-15',5 'Content-Type': 'application/json'6};Error 400: 'revision header is required' or 'invalid revision'
Cause: Klaviyo's v2023+ API requires a 'revision' header specifying the API version date. Omitting this header or using an outdated version string causes the request to fail.
Solution: Add the revision header to all Klaviyo API requests. Use '2024-10-15' for current stable API access. Check Klaviyo's developer changelog at developers.klaviyo.com for the latest supported revision dates.
1// Always include the revision header2const headers = {3 'Authorization': `Klaviyo-API-Key ${KLAVIYO_KEY}`,4 'revision': '2024-10-15', // Required β use current stable revision5 'Content-Type': 'application/json'6};Profile created successfully but does not appear in Klaviyo's Profiles list
Cause: Klaviyo may have a processing delay for profile-import operations, or the profile was created with an email format that Klaviyo did not recognize. Klaviyo also has anti-spam filters that may suppress profiles it considers suspicious.
Solution: Wait a few minutes after profile-import calls, as they may be queued. Verify the email address is valid (proper format, no test@test.com placeholder addresses). Search in Klaviyo by the exact email address rather than browsing the full list. Check Klaviyo's import log for any rejection notices.
Event tracked successfully but Klaviyo flow is not triggering
Cause: The event name in the API call does not exactly match the trigger configured in the Klaviyo flow, the flow is in draft mode rather than live, or the profile does not meet the flow's additional filter conditions.
Solution: In Klaviyo, go to Flows β your flow β check that it is set to 'Live' (not Draft). Verify the trigger metric name matches exactly what you are sending in the event name field β Klaviyo is case-sensitive. Check the flow's additional filter conditions and ensure the profile meets them. Use Klaviyo's Activity feed for the profile to confirm the event was received.
Best practices
- Store KLAVIYO_PRIVATE_KEY in Replit Secrets (lock icon π) β the private key provides full API access to your Klaviyo account including sending emails and accessing customer data
- Always include the 'revision' header on every Klaviyo API request to ensure consistent API behavior even as Klaviyo updates their API
- Use the /profile-import/ endpoint for creating/updating profiles from your backend β it automatically handles deduplication by email address
- Track the $value property in order events so Klaviyo correctly attributes revenue to email campaigns and flows in its analytics dashboards
- Verify Klaviyo webhook signatures using the X-Klaviyo-Signature header before processing events to prevent unauthorized requests to your endpoint
- Deploy as Autoscale or Reserved VM to keep webhook endpoints available 24/7 β Klaviyo's webhook retry policy is limited, and missed events can cause subscription state to get out of sync
- Create Klaviyo API keys with minimum required scopes (Profiles, Events, Lists) rather than full-access keys β this limits damage if a key is ever accidentally exposed
- Use Klaviyo's standard e-commerce event names ('Placed Order', 'Started Checkout') to leverage built-in flow templates rather than inventing custom event names that require manual flow setup
Alternatives
Mailchimp is a general-purpose email marketing platform with a more beginner-friendly interface and broader use cases beyond e-commerce, making it a better choice for non-commerce newsletters and one-off campaigns.
Constant Contact targets small businesses with simpler email marketing features and strong customer support, worth considering if your needs are basic and you want easier setup than Klaviyo's e-commerce focus.
Campaign Monitor has a clean API and design-focused email templates that appeal to agencies and brands prioritizing email aesthetics over deep e-commerce automation.
Frequently asked questions
How do I find my Klaviyo Private API key?
Log into Klaviyo, click your account name in the bottom-left corner, go to Settings β API Keys β Private API Keys. Click 'Create Private API Key', name it (e.g., 'Replit'), and select the needed scopes. Copy the key immediately β it starts with 'pk_' and is only shown once in full. Store it in Replit Secrets as KLAVIYO_PRIVATE_KEY.
Can I use Klaviyo with Replit for free?
Klaviyo has a free plan that supports up to 500 contacts and 150 email sends per month with full API access. This is enough for development and testing. The API key creation and usage is available on all Klaviyo plans including free. As your contact list grows, you move to paid plans based on contact count.
What is the difference between Klaviyo's Public and Private API keys?
The Public API key (also called Site ID or Company ID) is for client-side JavaScript tracking β it is safe to expose in browser code because it only allows tracking events for the current visitor. The Private API key is for server-side API calls β it has full read/write access to your Klaviyo account and must never appear in client-side code. Always use the Private key in Replit server code and store it in Replit Secrets.
How do I trigger a Klaviyo email flow from Replit?
Klaviyo flows are triggered by events, not called directly by API. To trigger a flow, track the event that the flow uses as its trigger. For example, if your flow triggers on 'Placed Order', call the Events API with event name 'Placed Order' for the customer's profile. Klaviyo automatically evaluates the event against all live flows and triggers matching ones. The flow must be set to 'Live' status, not 'Draft'.
What deployment type should I use for Klaviyo webhooks on Replit?
Use Autoscale deployment for Klaviyo webhook receivers. Klaviyo delivers webhook events asynchronously and has a retry policy for failed deliveries, so the brief cold start on Autoscale (1-3 seconds) is acceptable. Autoscale scales to zero when idle and costs nothing during quiet periods, making it economical for event-driven workloads like unsubscribe and bounce processing.
How do I subscribe a customer to a Klaviyo list from Replit?
First get or create the customer's profile to obtain their Klaviyo profile ID. Then call POST /api/lists/{list_id}/relationships/profiles/ with the profile ID in the request body. You can also use the Subscriptions API for explicit consent tracking. Find your List ID in Klaviyo under Lists & Segments β click your list β look in the URL or list settings.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation