--- title: Event Tracking Integration description: Track user behavior to trigger workflows icon: Activity --- ## Overview Track events from your application to trigger workflows and update contact data. Events like `user_signed_up`, `purchase_completed`, `feature_used` can start automated email sequences. ## Get your public key 1. Go to [Settings → General]({{DASHBOARD_URL}}/settings) 2. Copy your **Public Key** (starts with `pk_`) Public keys are safe to use in client-side code. ## Basic event tracking ### JavaScript (client-side) ```javascript await fetch('{{API_URL}}/v1/track', { method: 'POST', headers: { 'Authorization': `Bearer ${PLUNK_PUBLIC_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ event: 'button_clicked', email: user.email, data: { buttonName: 'Get Started', page: '/pricing' } }) }); ``` ### Node.js (server-side) ```javascript await fetch('{{API_URL}}/v1/track', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.PLUNK_PUBLIC_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ event: 'user_signed_up', email: user.email, data: { name: user.name, plan: 'free', signupDate: new Date().toISOString() } }) }); ``` ### Python ```python import requests import os requests.post('{{API_URL}}/v1/track', headers={ 'Authorization': f'Bearer {os.environ["PLUNK_PUBLIC_KEY"]}', 'Content-Type': 'application/json' }, json={ 'event': 'purchase_completed', 'email': user.email, 'data': { 'orderId': order.id, 'total': order.total, 'items': order.items } } ) ``` ## Common events to track ### User lifecycle ```javascript // Signup await trackEvent('user_signed_up', user.email, { name: user.name, source: 'google', plan: 'free' }); // Activation await trackEvent('first_value_achieved', user.email, { action: 'created_first_project', timestamp: new Date().toISOString() }); // Upgrade await trackEvent('subscription_upgraded', user.email, { fromPlan: 'free', toPlan: 'premium', mrr: 99 }); // Churn await trackEvent('subscription_cancelled', user.email, { reason: user.cancellationReason, cancelledAt: new Date().toISOString() }); ``` ### Product engagement ```javascript // Feature usage await trackEvent('feature_used', user.email, { featureName: 'data_export', timestamp: new Date().toISOString() }); // Content interaction await trackEvent('video_watched', user.email, { videoId: 'intro-101', duration: 300, completed: true }); // Settings changes await trackEvent('settings_updated', user.email, { setting: 'notifications', value: 'enabled' }); ``` ### E-commerce ```javascript // Cart await trackEvent('cart_abandoned', user.email, { cartId: cart.id, cartTotal: cart.total, items: cart.items.map(i => i.name) }); // Purchase await trackEvent('purchase_completed', user.email, { orderId: order.id, total: order.total, paymentMethod: 'credit_card' }); // Review await trackEvent('review_submitted', user.email, { productId: product.id, rating: 5 }); ``` ## Event naming conventions **Use lowercase with underscores:** - ✅ `user_signed_up` - ✅ `purchase_completed` - ❌ `UserSignedUp` - ❌ `purchase-completed` **Be specific:** - ✅ `trial_started` - ❌ `event` **Use past tense:** - ✅ `email_opened` - ❌ `email_open` ## Event data best practices **Keep data flat when possible:** ```javascript // Good { name: 'John', plan: 'premium', mrr: 99 } // Works but harder to use { user: { profile: { name: 'John' } } } ``` **Use consistent types:** ```javascript // Good - number for numeric values { total: 99.99 } // Bad - string for numeric values { total: "99.99" } ``` **Use ISO dates:** ```javascript // Good { signupDate: new Date().toISOString() } // Okay but less flexible { signupDate: '2024-03-15' } ``` ## Integrate with React ### Context provider ```javascript // EventTrackingContext.js import { createContext, useContext } from 'react'; const EventTrackingContext = createContext(); export function EventTrackingProvider({ children }) { const trackEvent = async (event, data = {}) => { const user = getCurrentUser(); // Your auth logic if (!user?.email) return; await fetch('{{API_URL}}/v1/track', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_PLUNK_PUBLIC_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ event, email: user.email, data: { name: user.name, ...data } }) }); }; return ( {children} ); } export const useEventTracking = () => useContext(EventTrackingContext); ``` ### Use in components ```javascript import { useEventTracking } from './EventTrackingContext'; function UpgradeButton() { const { trackEvent } = useEventTracking(); const handleUpgrade = async () => { await upgradePlan('premium'); await trackEvent('plan_upgraded', { plan: 'premium', source: 'pricing_page' }); }; return ; } ``` ## Integrate with Next.js ### Client component ```javascript 'use client'; import { trackEvent } from '@/lib/plunk'; export function SignupForm() { const handleSubmit = async (data) => { const user = await createUser(data); // Track event await trackEvent('user_signed_up', user.email, { name: user.name, source: 'homepage' }); }; return
...
; } ``` ### Server action ```javascript 'use server'; import { trackEvent } from '@/lib/plunk'; export async function createProject(formData) { const user = await getCurrentUser(); const project = await db.projects.create({ name: formData.get('name'), userId: user.id }); await trackEvent('project_created', user.email, { projectId: project.id, projectName: project.name }); return project; } ``` ## Create a helper function ```javascript // lib/plunk.js const PLUNK_PUBLIC_KEY = process.env.NEXT_PUBLIC_PLUNK_PUBLIC_KEY; export async function trackEvent(event, email, data = {}) { try { const response = await fetch('{{API_URL}}/v1/track', { method: 'POST', headers: { 'Authorization': `Bearer ${PLUNK_PUBLIC_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ event, email, data }) }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.json(); } catch (error) { console.error('Failed to track event:', error); // Don't throw - tracking shouldn't break your app } } ``` ## Testing events ### View tracked events 1. Go to **Activity** in Plunk dashboard 2. Filter by event type 3. View event data payloads ### Test locally ```javascript // Track a test event await fetch('{{API_URL}}/v1/track', { method: 'POST', headers: { 'Authorization': 'Bearer pk_your_key', 'Content-Type': 'application/json' }, body: JSON.stringify({ event: 'test_event', email: 'your-email@example.com', data: { test: true, timestamp: new Date().toISOString() } }) }); ``` Check Plunk Activity - event should appear within seconds. ## Connect to workflows Once events are tracked, create workflows that trigger on them: 1. **Workflows** → **Create Workflow** 2. Trigger: Event `user_signed_up` 3. Build your automation 4. Enable workflow Now when you track `user_signed_up`, the workflow runs automatically. ## Performance considerations **Don't block user actions:** ```javascript // Good - fire and forget handleClick() { trackEvent('button_clicked', user.email); // Don't await } // Bad - user waits for tracking async handleClick() { await trackEvent('button_clicked', user.email); // User has to wait } ``` **Batch events for bulk operations:** ```javascript // If importing 1000 users, track events in background async function importUsers(users) { const imported = await db.users.bulkCreate(users); // Queue for background processing await queue.add('track-events', { event: 'user_imported', users: imported }); } ``` **Add retry logic:** ```javascript async function trackEventWithRetry(event, email, data, retries = 3) { for (let i = 0; i < retries; i++) { try { return await trackEvent(event, email, data); } catch (error) { if (i === retries - 1) throw error; await new Promise(r => setTimeout(r, 1000 * (i + 1))); } } } ``` ## Common issues **Event tracked but workflow not triggering** - Workflow is enabled - Event name matches exactly (case-sensitive) - Contact exists in Plunk - Contact is subscribed **CORS errors in browser** - Use public key (not secret key) - Plunk API allows CORS from all origins **Contact not created** - Email must be valid - Contact is created automatically when event is tracked ## Next steps - [Build a workflow](/tutorials/welcome-series-workflow) triggered by events - [Campaigns vs Workflows](/concepts/campaigns-vs-workflows) decision guide - [Stripe integration](/integrations/stripe-billing) for billing events