How to Track Custom Events in Your Vibe-Coded App
Pageviews tell you who showed up. Custom event tracking tells you what they actually did. Here's how to use vibeping.track() to measure signups, CTA clicks, and checkout funnels in your vibe-coded app.
Pageviews are great. They tell you people showed up. But they don't tell you the thing that actually matters: what did those people do?
Did they click the signup button? Did they finish the onboarding flow? Did they start checkout and then bail? You have no idea — unless you're tracking custom events.
What custom event tracking actually is
A custom event is you telling your analytics tool: "hey, this specific thing just happened." A user clicked a button. Someone completed a form. A payment went through. Whatever matters to your app.
VibePing's automatic tracking already handles pageviews, errors, and web vitals out of the box. Custom events are for everything else — the business-specific stuff that's unique to your app.
The API is one function call:
vibeping.track('event_name', { key: 'value' })First argument is the event name. Second is an optional object with whatever metadata you want attached. That's the whole API.
Why this matters more for vibe-coded apps
If you built your app with Lovable, Bolt.new, or Cursor, you probably shipped fast. Really fast. Which is awesome — but it also means you probably skipped the part where you think about measurement.
Traditional dev teams spend weeks setting up analytics pipelines. You don't need that. But you do need to know if your app is actually converting users or just collecting pageviews.
Here's a scenario: you built a SaaS app with Lovable. You're getting 500 visitors a day. Feels good. But only 3 people signed up last week. Is your signup flow broken? Is the CTA invisible? Is the pricing page scaring people off?
Without custom event tracking, you're guessing. With it, you can see exactly where people drop off.
Tracking a signup flow
Let's say your app has a two-step signup: users click "Get Started," then complete a registration form. Here's how you'd track that:
// User clicks the signup button
vibeping.track('signup_started', { method: 'google' })
// User completes registration
vibeping.track('signup_completed', { plan: 'free' })Now you can see: 200 people started signup this week. 45 completed it. That's a 22% completion rate. Maybe your form is too long. Maybe Google auth is failing silently. You've got a number to work with instead of a gut feeling.
If you're using email + password alongside OAuth, track the method so you can compare:
vibeping.track('signup_started', { method: 'email' })
vibeping.track('signup_started', { method: 'google' })
vibeping.track('signup_started', { method: 'github' })You might find that Google sign-in converts at 60% while email converts at 15%. That's actionable data — maybe you should make the Google button more prominent.
Tracking CTA clicks
You've got buttons on your landing page. A "Get Started" button in the hero. Another one at the bottom. Maybe one on the pricing page. Are any of them actually getting clicked?
vibeping.track('cta_clicked', { button: 'Get Started', page: '/pricing' })vibeping.track('cta_clicked', { button: 'Get Started', page: '/hero' })vibeping.track('cta_clicked', { button: 'Start Free Trial', page: '/features' })Track the button label and the page it's on. Now you can answer questions like: does the pricing page CTA get more clicks than the hero CTA? If you A/B test a different button label, you can measure if it made a difference.
This is dead simple stuff, but most vibe-coded apps don't do it. They ship a landing page, drive traffic to it, and have zero visibility into what's working.
Tracking a checkout funnel
This is where custom events get really powerful. A checkout funnel usually has multiple steps, and users can drop off at any of them:
// User clicks "Upgrade to Pro"
vibeping.track('checkout_started', { plan: 'pro', price: 9 })
// User enters payment details
vibeping.track('checkout_payment_entered', { plan: 'pro' })
// Payment succeeds
vibeping.track('checkout_completed', { plan: 'pro' })Three events. Three points where you can measure drop-off. If 100 people start checkout but only 20 enter payment info, your payment form is the problem. If 20 enter payment info but only 5 complete, maybe there's an error in your Stripe integration you don't know about.
This is the kind of insight that directly affects revenue. And it takes about 10 minutes to set up.
Naming conventions that won't haunt you later
Your future self will thank you for being consistent with event names. Here are some rules that work well:
Use snake_case. Not camelCase, not kebab-case. signup_completed, not signupCompleted or signup-completed. Pick one and stick with it.
Use past tense or verb_noun format. signup_completed is clearer than signup or complete_signup. When you're scanning a list of events, you want to immediately know what happened.
Be specific with metadata. Don't shove everything into the event name. checkout_completed with { plan: 'pro' } is better than checkout_completed_pro. The metadata is filterable — the event name shouldn't change based on context.
Keep a running list. Seriously, just a text file or a Notion doc. List every event name and what it means. When you're vibe-coding fast and adding events on the fly, you will accidentally create signup_complete and signup_completed as separate events. A reference list prevents this.
Here's a starter set for a typical SaaS:
// Signup flow
vibeping.track('signup_started', { method: 'google' })
vibeping.track('signup_completed', { plan: 'free' })
// Core actions
vibeping.track('cta_clicked', { button: 'Get Started', page: '/pricing' })
vibeping.track('onboarding_completed', { steps_total: 3 })
vibeping.track('feature_used', { feature: 'export_csv' })
// Checkout flow
vibeping.track('checkout_started', { plan: 'pro', price: 9 })
vibeping.track('checkout_completed', { plan: 'pro' })
// Engagement
vibeping.track('invite_sent', { method: 'email' })
vibeping.track('feedback_submitted', { rating: 5 })Where to put the track calls
If you're working in React (which most Lovable and Bolt.new apps are), you'll typically fire events in click handlers and useEffect hooks:
<button onClick={() => {
vibeping.track('cta_clicked', { button: 'Get Started', page: '/pricing' })
router.push('/signup')
}}>
Get Started
</button>For events that happen after an async action (like a successful API call), put them in the success handler:
const handleSignup = async (formData) => {
const result = await createAccount(formData)
if (result.success) {
vibeping.track('signup_completed', { plan: formData.plan })
}
}Don't overthink placement. The event should fire when the thing actually happens. Button click? Fire on click. Form submission succeeds? Fire in the success callback. Page loads? Fire in a useEffect.
Start with five events
You don't need to track everything on day one. Pick the five events that matter most to your app right now. For most SaaS apps, that's:
- Signup started
- Signup completed
- Main CTA clicked
- Core feature used (whatever your "aha moment" is)
- Checkout completed (if you have a paid plan)
Add those five. Check your VibePing dashboard after a few days. You'll immediately see patterns you couldn't see before — and you'll know exactly which events to add next.
Custom event tracking isn't complicated. It's a one-line function call. But the difference between "500 pageviews" and "500 pageviews, 80 signups started, 20 completed, 5 upgraded to Pro" is the difference between guessing and knowing.
Stop guessing. Add VibePing↗ to your app and start tracking what actually matters.