Skip to content

Conversation

@perkinsjr
Copy link
Collaborator

@perkinsjr perkinsjr commented Dec 4, 2025

What does this PR do?

This updates the Stripe webhook to check if anything changed and if not just early exit. This will stop it from firing on 1st of the month but still fire for everything else.

Fixes # (issue)

#4441

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

Join stripe-test via invite
Subscriptions on first of the month:

Put card in.
Setup a subscription
Use fast forward to go to the 1st of January
Make sure that the payment for that month goes through
No notifications

  • Upgrade an account (notification)
  • Downgrade an account (notification)
  • Cancel (notification)

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Ran make fmt on /go directory
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

perkinsjr and others added 4 commits December 1, 2025 14:37
This makes stripe more robust.

It checks for actual changes before firing webhooks, and also checks if
we should auto apply the updates to quotas based upon a DB entry.
@changeset-bot
Copy link

changeset-bot bot commented Dec 4, 2025

⚠️ No Changeset found

Latest commit: ff0dde8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Dec 4, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
dashboard Error Error Dec 9, 2025 4:39pm
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
engineering Ignored Ignored Preview Dec 9, 2025 4:39pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

📝 Walkthrough

Walkthrough

Detects automated Stripe subscription renewals and early-exits the webhook handler for those events, skipping database updates and notifications; non-automated subscription updates continue with explicit quota and workspace tier updates inside a transaction and a dedicated notification step.

Changes

Cohort / File(s) Change Summary
Automated Billing Renewal Detection
apps/dashboard/app/api/webhooks/stripe/route.ts
Added PreviousAttributes interface and isAutomatedBillingRenewal(sub, previousAttributes) helper to identify automated renewals by active status and exact changed keys. Modified customer.subscription.updated handling to early-exit with a "Skip" response and HTTP 201 for automated renewals, bypassing DB updates and notifications. Restructured non-automated path to perform quota and workspace tier updates inside a transaction, then send a dedicated subscription update notification; added clarifying comments.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Stripe as Stripe (Webhook)
participant Handler as App Webhook Handler
participant DB as Database (Transaction)
participant Notifier as Notification Service

Stripe->>Handler: POST customer.subscription.updated (sub, previous_attributes)
Handler->>Handler: isAutomatedBillingRenewal(sub, previous_attributes)?
alt Automated renewal detected
    Handler-->>Stripe: 201 "Skip"
    note right of Handler: No DB updates\nNo notifications
else Non-automated update
    Handler->>DB: Begin transaction
    Handler->>DB: Update quotas & workspace tier
    DB-->>Handler: Commit
    Handler->>Notifier: Send subscription update notification
    Notifier-->>Handler: Acknowledged
    Handler-->>Stripe: 200 OK

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review isAutomatedBillingRenewal() logic for edge cases (missing/extra keys in previous_attributes)
  • Confirm the 201 "Skip" response behavior aligns with webhook retry policies and monitoring
  • Verify transactional updates for quotas and workspace tier (atomicity, error handling, rollback)
  • Validate the new standalone notification step (timing, payload consistency)

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'chore: Make Stripe Great Again' is vague and generic, using colloquial phrasing that doesn't convey specific information about the actual changes. Update the title to be more specific about the change, such as 'chore: Skip Stripe renewal notifications on automated billing' to clearly describe what was modified.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is mostly complete with all required sections filled out, including detailed testing instructions, type of change, and a comprehensive checklist.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-stripe-properly

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5db3b1 and ff0dde8.

📒 Files selected for processing (1)
  • apps/dashboard/app/api/webhooks/stripe/route.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/app/api/webhooks/stripe/route.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Dashboard / Test Dashboard
  • GitHub Check: Analyze (javascript-typescript)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/dashboard/app/api/webhooks/stripe/route.ts (1)

154-157: Add logging for observability when skipping automated renewals.

When silently returning early, debugging becomes difficult if something goes wrong. Consider adding a debug log to track when renewals are skipped:

        // Skip database updates and notifications for automated billing renewals
        if (isAutomatedBillingRenewal(sub, previousAttributes)) {
+          console.log(`Skipping automated billing renewal for subscription: ${sub.id}`);
          return new Response("OK");
        }

This will help with debugging and monitoring the behavior in production.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0740117 and 5af7040.

📒 Files selected for processing (1)
  • apps/dashboard/app/api/webhooks/stripe/route.ts (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-08T15:33:04.290Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 2120
File: apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx:19-19
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In the `StripeSuccess` function, TypeScript's type-checking of the `new_plan` parameter ensures that only "free", "pro", or undefined values are accepted, so additional runtime validation is not necessary.

Applied to files:

  • apps/dashboard/app/api/webhooks/stripe/route.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Dashboard / Test Dashboard
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
apps/dashboard/app/api/webhooks/stripe/route.ts (3)

14-34: Interface definition looks reasonable.

The interface correctly models the expected fields from Stripe's previous_attributes payload. Note that Stripe's actual type is a partial of the subscription object, so this custom interface provides clearer typing for the specific fields being checked.


204-204: Clarifying comment is helpful.


286-286: Clarifying comment is helpful.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/dashboard/app/api/webhooks/stripe/route.ts (1)

36-63: Consider a more resilient detection approach for automated renewals.

As noted in the previous review, the current exact-match logic is brittle. Stripe's subscription.updated webhook can include additional fields beyond just current_period_start and current_period_end during legitimate automated renewals (e.g., billing_cycle_anchor, nested items changes). This will cause the function to return false for actual automated renewals, leading to unnecessary database updates and notifications.

Consider inverting the logic to check for the absence of meaningful change keys (such as items, plan, quantity, discount, cancel_at_period_end, collection_method) rather than requiring an exact match of expected keys. This approach is more forward-compatible and resilient to Stripe's evolving webhook payloads.

🧹 Nitpick comments (1)
apps/dashboard/app/api/webhooks/stripe/route.ts (1)

154-157: Add logging and use standard status code for observability.

The early exit lacks logging, making it difficult to monitor how often automated renewals are being skipped. Additionally, returning a 201 status with "Skip" is non-standard for webhook handlers.

Apply this diff to improve observability and use standard conventions:

 // Skip database updates and notifications for automated billing renewals
 if (isAutomatedBillingRenewal(sub, previousAttributes)) {
+  console.log(`Skipping automated renewal for subscription: ${sub.id}, workspace: ${ws.id}`);
-  return new Response("Skip", { status: 201 });
+  return new Response("OK", { status: 200 });
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5af7040 and a5db3b1.

📒 Files selected for processing (1)
  • apps/dashboard/app/api/webhooks/stripe/route.ts (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-08T15:33:04.290Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 2120
File: apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx:19-19
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In the `StripeSuccess` function, TypeScript's type-checking of the `new_plan` parameter ensures that only "free", "pro", or undefined values are accepted, so additional runtime validation is not necessary.

Applied to files:

  • apps/dashboard/app/api/webhooks/stripe/route.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Dashboard / Test Dashboard
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/dashboard/app/api/webhooks/stripe/route.ts (2)

14-34: LGTM: Interface structure is adequate.

The PreviousAttributes interface appropriately models the Stripe subscription fields that can appear in previous_attributes. The comments help distinguish between automated renewal fields and manual change fields.


204-204: LGTM: Clarifying comments improve readability.

The added comments clearly describe the distinct steps in the subscription update workflow, making the code easier to follow.

Also applies to: 286-286

Copy link
Collaborator

@mcstepp mcstepp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeet

@perkinsjr perkinsjr merged commit 1545442 into main Dec 9, 2025
21 of 22 checks passed
@perkinsjr perkinsjr deleted the fix-stripe-properly branch December 9, 2025 16:43
mcstepp pushed a commit that referenced this pull request Dec 9, 2025
* fix: Make stripe webhooks more robust
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants