Custom event goals let you track any action by firing JavaScript events. Perfect for forms, video plays, and complex interactions.
When to Use Custom Events
Best for tracking:
- Form submissions (AJAX)
- Video interactions (play, complete)
- Single-page app navigation
- Add to cart actions
- Feature usage in web apps
- Any JavaScript-triggered action
How Custom Events Work
- You add JavaScript code to your site
- Code fires when action occurs
- Zenovay receives the event
- Goal is recorded
Basic Implementation
Tracking an Event
// Simple event
zenovay('goal', 'event_name');
// Event with value
zenovay('goal', 'purchase', { value: 99.99 });
// Event with value and currency
zenovay('goal', 'purchase', {
value: 99.99,
currency: 'USD'
});
A goal call records value and currency. Any other keys you pass are sent with the underlying event payload, but the goal itself is reported by its name and value.
Creating the Goal
- Open your website's dashboard and select the Journeys tab
- Open the Goals sub-tab and click Add Goal
- Choose Custom Event
- Enter the event name (e.g.,
purchase) so it matches the name you fire in code - Optionally turn on value tracking and set a default value
- Save
Custom event goals also auto-create the first time Zenovay receives an event name it hasn't seen, so you can fire events first and they will appear in the Goals list.
Event Naming
Best Practices
| Good Names | Bad Names |
|---|---|
signup_complete | goal1 |
purchase | click |
video_50_percent | event |
add_to_cart | 123 |
Naming Conventions
Choose a consistent pattern:
snake_case:form_submitcamelCase:formSubmitkebab-case:form-submit
Categories with Prefixes
Organize events with prefixes:
// E-commerce events
zenovay('goal', 'ecom_add_to_cart');
zenovay('goal', 'ecom_checkout_start');
zenovay('goal', 'ecom_purchase');
// Engagement events
zenovay('goal', 'engage_video_play');
zenovay('goal', 'engage_scroll_50');
Common Implementations
Form Submission
document.querySelector('form').addEventListener('submit', function(e) {
zenovay('goal', 'contact_form');
});
// Or for AJAX forms
function onFormSuccess() {
zenovay('goal', 'contact_form');
}
Video Interactions
const video = document.querySelector('video');
video.addEventListener('play', function() {
zenovay('goal', 'video_play');
});
video.addEventListener('ended', function() {
zenovay('goal', 'video_complete');
});
// Track 50% progress
video.addEventListener('timeupdate', function() {
if (video.currentTime / video.duration > 0.5) {
zenovay('goal', 'video_50_percent');
}
});
E-commerce Actions
// Add to cart
function addToCart(product) {
// Your cart logic...
zenovay('goal', 'add_to_cart', {
value: product.price
});
}
// Purchase complete
function onPurchaseComplete(order) {
zenovay('goal', 'purchase', {
value: order.total,
currency: 'USD'
});
}
Sign Up / Registration
async function handleSignup(formData) {
try {
const response = await api.signup(formData);
if (response.success) {
zenovay('goal', 'signup_complete', {
value: 50 // Estimated lead value
});
}
} catch (error) {
// Handle error
}
}
Passing Values
Static Value
Set in the goal's settings:
- Turn on value tracking
- Enter a default value (e.g. 50)
- Every completion counts toward that value
Dynamic Value
Pass from your code:
// Order value
zenovay('goal', 'purchase', {
value: orderTotal
});
// Calculated value
const leadValue = isPremium ? 100 : 25;
zenovay('goal', 'lead', { value: leadValue });
Value from Page
Read from the DOM:
const price = parseFloat(
document.querySelector('.price').textContent.replace('$', '')
);
zenovay('goal', 'purchase', { value: price });
Goal Value Fields
A goal completion records these fields:
| Property | Type | Example |
|---|---|---|
| value | number | 99.99 |
| currency | string | "USD" |
Pass value as a plain number (no currency symbol or string). currency is an ISO currency code and defaults to USD.
Framework Integrations
React
import { useCallback } from 'react';
function SignupForm() {
const handleSubmit = useCallback((data) => {
// Submit logic...
window.zenovay('goal', 'signup');
}, []);
return <form onSubmit={handleSubmit}>...</form>;
}
Vue
<script>
export default {
methods: {
handleSubmit() {
// Submit logic...
window.zenovay('goal', 'signup');
}
}
}
</script>
Next.js
// Client-side tracking inside a component
'use client';
export default function CheckoutButton() {
function handleSuccess() {
if (typeof window !== 'undefined') {
window.zenovay('goal', 'purchase');
}
}
return <button onClick={handleSuccess}>Complete</button>;
}
Single Page Apps (SPAs)
Route Change Tracking
For virtual pageviews:
// React Router
history.listen(() => {
zenovay('page');
});
// Vue Router
router.afterEach(() => {
zenovay('page');
});
Goal After Navigation
// Navigate then track
router.push('/success').then(() => {
zenovay('goal', 'signup_complete');
});
Conditional Tracking
Track Based on Conditions
// Only track if conditions met
if (user.isNewUser && !user.hasConvertedBefore) {
zenovay('goal', 'first_conversion');
}
// A/B test variant
if (experimentVariant === 'B') {
zenovay('goal', 'variant_b_conversion');
}
Debouncing Events
Prevent duplicate fires:
let hasTracked = false;
function trackOnce(eventName) {
if (!hasTracked) {
zenovay('goal', eventName);
hasTracked = true;
}
}
Error Handling
Safe Tracking
Handle cases where Zenovay isn't loaded yet. The tracking snippet defines a small command queue, so calls made before the script finishes loading are still processed once it does:
function safeTrackGoal(eventName, properties) {
if (typeof zenovay === 'function') {
zenovay('goal', eventName, properties);
} else {
console.warn('Zenovay not loaded');
}
}
Testing Events
Debug Mode
Enable console logging:
zenovay('debug');
// Now track something
zenovay('goal', 'test_event');
// Check console for output
Verify in the Dashboard
- Fire a test event
- Open your website's dashboard and select the Live View tab to watch activity in real time, or open Journeys → Goals to see the goal's completion count
- Verify the goal appears and the count increases
- Check the goal value
Network Tab
- Open DevTools → Network
- Fire the event
- Look for a request to
api.zenovay.com - Verify the payload
Troubleshooting
Events Not Recording
Check script loaded:
console.log(typeof zenovay); // Should be 'function'
Check event name matches:
- Goal:
signup - Code:
zenovay('goal', 'signup')✓ - Code:
zenovay('goal', 'Signup')✗
Check for errors:
- Browser console for errors
- Network tab for failed requests
Value Not Recording
Ensure value is a number:
// Wrong
zenovay('goal', 'purchase', { value: "$99.99" });
// Correct
zenovay('goal', 'purchase', { value: 99.99 });