
Your AI-built SaaS has a pricing page, a checkout and a satisfying little badge that says Pro. A customer pays. Confetti appears. The founder briefly experiences peace.
Then the renewal fails, the payment provider sends the same notification twice, a cancellation is scheduled for next month and two events arrive in the opposite order. The badge remains cheerful. The account, however, has no settled opinion about whether this person is a customer.
This is the unglamorous centre of SaaS subscription management: keeping the commercial agreement, the billing provider and the access inside your product in step. AI tools can build the checkout quickly. The serious work is defining what every billing state means, processing asynchronous events safely and proving that paid features switch on and off at the correct time.
A successful checkout is not a subscription system
Checkout proves that one payment journey completed. A subscription continues afterwards, usually without the customer or your application watching it. Cards expire. Banks require further authentication. Invoices fail and later recover. Plans change. Customers cancel now or at the end of a paid period.
Billing platforms report much of this activity through webhooks: signed HTTP notifications sent to your application. Stripe's subscription webhook guidance says subscription activity is largely asynchronous and requires webhook handling. Its documented subscription statuses include states such as incomplete, active, past_due, unpaid, paused and canceled. Those words are not decorative provider trivia. They are inputs to product access, customer messages, support work and revenue records.
The first design job is therefore to separate three related facts:
- what the billing provider currently says about the subscription;
- what access policy your business applies to that state; and
- what access the product has actually granted.
If those are represented by one improvised boolean called isPro, it will eventually be asked to explain a failed renewal, a grace period, a scheduled cancellation and a refund. This is ambitious work for a checkbox.
Define paid access before writing more webhook code
There is no universal rule for every SaaS. A failed renewal might begin a short grace period, move the account to read-only access or remove paid features immediately. The right answer depends on the product, customer promise, risk and billing terms. It must be an explicit business decision rather than whatever an AI coding assistant inferred from the last event name it saw.
Write a small access policy for each meaningful state. It might say that a trial and an active subscription receive their purchased entitlements; a past-due account keeps access for a defined recovery period with a clear billing notice; a subscription scheduled to cancel remains active until its paid-through time; and a completed cancellation loses paid features while retaining whatever data access the product has promised.
Paddle's current subscription access guidance illustrates the distinction. It recommends keeping access for an automatically collected subscription while it is past_due, then revoking when it becomes canceled; a future scheduled cancellation should keep access until it takes effect. Stripe likewise distinguishes cancellation now from cancellation at the end of the current billing period. Your provider's exact states and settings may differ, so copy the principle, not a status table from an unrelated account.
Also decide what customers see. “Payment failed” should lead to a useful recovery route. “Cancels on 14 November” should not look like “access removed today”. Support staff need the same state and dates that the product is using, or each ticket becomes a small forensic inquiry.
Handle duplicate and out-of-order notifications
A fragile handler reads an event, changes the account and assumes it will never see that event again. Real delivery is less ceremonial. Stripe's webhook documentation describes automatic retries for up to three days in live mode, says delivery order is not guaranteed and warns that the same event can arrive more than once. Paddle documents at-least-once delivery and out-of-order events too.
Build the handler around those facts:
- Verify the sender. Validate the provider's signature against the unmodified request body before trusting the event.
- Record the event durably. Store or enqueue the event with its provider event ID, object ID, type and occurrence time.
- Acknowledge promptly. Return success without making the provider wait while emails, analytics and account updates conduct a committee meeting.
- Deduplicate processing. Put a uniqueness rule around the event ID so a retry cannot grant access twice, create duplicate subscription records or send repeated messages.
- Resolve current state. Apply provider-specific ordering rules or retrieve the current subscription object before changing access. Arrival order is not business truth.
- Make the transition repeatable. Processing the same valid state again should leave the same result, with an audit record rather than an additional side effect.
Make business effects idempotent too: different event IDs can describe the same change. Record processing state so a crash can be retried safely, and protect concurrent updates from overwriting newer state.
Stripe calls this concern out directly: track event IDs for duplicate deliveries and do not depend on receiving events in generation order. Paddle advises comparing its occurred_at value with the last stored update and running periodic reconciliation. The implementation differs, but the product lesson is stable. A webhook is evidence that something happened. It is not permission to move your local account backwards in time.
The awkward cases are the acceptance test
A launch test that buys one plan with the provider's favourite test card proves only the path already chosen for the demonstration. A useful subscription test fixture creates a fictional customer, a known plan and an observable local access record, then exercises the less obliging routes.
- The first payment fails or requires customer action. Confirm that paid access is not granted prematurely and the customer can recover.
- A renewal fails, remains past due and later succeeds. Check the chosen grace policy, messages and restoration of the correct entitlements.
- The identical webhook is delivered twice. Confirm one recorded transition, one access result and no repeated customer communication.
- A later event arrives before an earlier one. Confirm the account does not regress from paid to pending because the network shuffled the envelopes.
- A customer cancels at period end, then reverses that decision before expiry. Check the paid-through date and continued access.
- A subscription is cancelled immediately. Verify access, outstanding work, downloads and customer data follow the policy you actually promised.
- The webhook worker is unavailable, then recovers. Replay the recorded events and reconcile local state against the provider.
Stripe now provides Billing simulations using test clocks for cases such as renewal failures and lifecycle events. Paddle provides a webhook simulator. These tools are helpful, but your test must still assert the product consequence. A provider dashboard showing the correct status does not prove that your application removed the right feature from the right account.
Reconciliation is the cure for quiet drift
Even a careful event pipeline can be interrupted by a deployment error, disabled endpoint, expired secret or defective release. Build a reconciliation job that periodically compares the subscriptions relevant to access with the local projection used by the product. Report differences, repair them through the same controlled transition logic and retain evidence of the change.
Monitoring should describe commercial consequences, not merely server health. Track unprocessed events, repeated failures, subscriptions with no matching account, accounts whose entitlements disagree with billing state and cancellations that have passed their effective time without access changing. Give alerts a named owner. A queue can be technically healthy while paid customers remain locked out with impressive consistency.
This is one example of the wider gap between an AI prototype and a production product. It is also why a useful AI-built product audit tests business journeys and reproducible failure cases, rather than admiring the checkout screen from a respectful distance.
Paid access should be a policy, not a side effect
AI can help build billing integration code. It cannot decide how your company treats a customer during payment recovery, what cancellation promise was made or which evidence support needs when states disagree. Those decisions belong in a visible access policy, a tested state model and an observable event process.
If your SaaS takes subscription payments but paid access still depends on a boolean, a single webhook or assumptions about event order, show Venturist what you have built. We can arrange a free introductory product review focused on the billing journey, failure handling and the gaps between provider state and customer access.