Skip to content

customer.subscription.updated webhook doesn't update priceId field #7

@Dan-Cleary

Description

@Dan-Cleary

When a user upgrades or downgrades their subscription (to a different product, not just adding/removing seats) via the Customer Portal, the customer.subscription.updated webhook fires and handleSubscriptionUpdated runs successfully, but the priceId field on the subscription record is not updated.

This breaks any logic that relies on priceId to determine the user's current plan tier.

Steps to Reproduce

  1. Create a subscription with priceId: "price_pro_monthly"
  2. Open Customer Portal via createCustomerPortalSession()
  3. Upgrade to a different plan (e.g., Team)
  4. Stripe fires customer.subscription.updated webhook
  5. Check the subscription record in Convex

Expected: priceId is updated to "price_team_monthly"
Actual: priceId is still "price_pro_monthly"

Root Cause

In index.ts, the customer.subscription.created handler passes priceId:

case "customer.subscription.created": {
  await ctx.runMutation(component.private.handleSubscriptionCreated, {
    // ...
    priceId: subscription.items.data[0]?.price.id || "",  // ✅ Included
  });
}But `customer.subscription.updated` does not:

case "customer.subscription.updated": {
  await ctx.runMutation(component.private.handleSubscriptionUpdated, {
    stripeSubscriptionId: subscription.id,
    status: subscription.status,
    currentPeriodEnd: subscription.items.data[0]?.current_period_end || 0,
    cancelAtPeriodEnd: subscription.cancel_at_period_end ?? false,
    quantity: subscription.items.data[0]?.quantity ?? 1,
    metadata: subscription.metadata || {},
    // ❌ priceId NOT passed
  });
}

Additionally, the handleSubscriptionUpdated mutation in private.ts doesn't accept priceId in its args.

Suggested Fix

  1. index.ts - Add priceId to the updated handler:
case "customer.subscription.updated": {
  await ctx.runMutation(component.private.handleSubscriptionUpdated, {
    // ... existing fields ...
    priceId: subscription.items.data[0]?.price.id,  // Add this
  });
}2. **`private.ts`** - Accept and save priceId in the mutation:
handleSubscriptionUpdated: mutation({
  args: {
    // ... existing args ...
    priceId: v.optional(v.string()),  // Add this
  },
  handler: async (ctx, args) => {
    // ... in db.patch() ...
    ...(args.priceId && { priceId: args.priceId }),
  },
});

Environment

  • @convex-dev/stripe version: latest (
  • Stripe API version: 2025-11-17.clover

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions