---
title: "Avoiding plan upgrade misattribution | Cometly Academy"
description: "A common PLG bug: when an existing customer upgrades from a Pro plan to an Enterprise plan, naive Stripe webhooks fire as a new subscription. Reports balloon with phantom trials and conversions. The fix is filtering the `subscription.updated` webhook to only fire when status moves from `trialing` to `active` — a definitive signal of a real trial conversion."
source: "https://www.cometly.com/academy/avoiding-plan-upgrade-misattribution"
---

# Avoiding plan upgrade misattribution

Why your reports show twice as many trials as they should.

- Module 03: Product-Led Growth Reports
- Lesson: 3.6
- Track: PLG
- Format: Setup
- Read time: 8 min

A common PLG bug: when an existing customer upgrades from a Pro plan to an Enterprise plan, naive Stripe webhooks fire as a new subscription. Reports balloon with phantom trials and conversions. The fix is filtering the `subscription.updated` webhook to only fire when status moves from `trialing` to `active` — a definitive signal of a real trial conversion.

## Key takeaways

- Use `subscription.updated` with the `previous_attributes.status = trialing` filter
- Tag plan upgrades and downgrades as their own events for accurate expansion-revenue reporting
- Never use simple payment-amount filters as a trial-conversion signal — they capture upgrades too
- Audit historical trials monthly to catch silent regressions in event firing
- Keep a 'No Source' bucket visible so you can spot tracking gaps fast

This is one of the most common silent bugs we see in PLG attribution setups. The reports look fine, the numbers grow predictably, and three months later someone notices that trial counts in Cometly are double the trial counts in Stripe. Almost always, it’s plan upgrades getting tagged as trials.

## Why it matters

When the bug is live, every plan upgrade in your customer base looks like a new trial conversion. Channel ROAS gets inflated by 30–80%, lookalike seeds become contaminated with existing customers, and the platforms’ algorithms start optimizing toward upgrades instead of new acquisition.

## The right trigger

Use Stripe’s `customer.subscription.updated` event, filtered to records where `previous_attributes.status = 'trialing'` and `current.status = 'active'`. That filter only fires on real trial-to-paid conversions, not on plan changes.

Tag plan upgrades and downgrades as their own events (Upgrade, Downgrade, Plan Changed). That way the attribution doesn’t collide with the new-customer events, and you can build separate expansion-revenue reports for upgrades.

- Trigger: `customer.subscription.updated`
- Filter: `previous_attributes.status` = 'trialing' AND `status` = 'active'
- Tag plan changes (`previous_attributes.plan` ≠ current `plan`) as Upgrade/Downgrade
- Audit monthly against Stripe’s native trial-conversion count

## Auditing the setup

Run a monthly audit: count Stripe trial-to-paid conversions in the last 30 days from Stripe’s native dashboard, then count Cometly Trial Converted events in the same window. They should match within 1–2%. A 10%+ delta means something is misfiring — either the filter is too loose (counting upgrades) or too tight (missing real conversions).

## Common pitfalls

### Using payment amount as a trial-conversion filter

Plan upgrades match payment-amount filters. Always use the status-transition filter instead.

### Letting upgrades and new customers share an event

Expansion revenue and new acquisition are different problems. Separate them at the event level.

### Skipping the monthly audit

These bugs are silent. You only catch them with a routine reconciliation against Stripe’s native dashboard.