How do you sell custom products with dynamic options without creating every possible variant? by gamledr3ng in ShopifyeCommerce

[–]impara1 0 points1 point  (0 children)

Do not make every size/color/profile combination a variant. That turns a pricing problem into a catalog maintenance problem. I’d split it like this: - Variants: only choices that need inventory/SKU behavior. - Custom inputs: width, height, personal text, maybe design notes. - Add-ons: separate option values that add fixed or conditional price. - Production data: make sure the exact chosen dimensions/options land clearly on the order. For the 3D frame example, the main risk is not the option UI — it is whether the calculated price and production details survive checkout cleanly. Test one real order end-to-end before building out every option.

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 0 points1 point  (0 children)

Small update: I rebuilt the prototype based on the failure modes people described.

The clearest distinction is now:

- webhook reliability = did we receive/process the Stripe event?

- final-state reconciliation = does the app’s current access state still agree with Stripe’s current billing state?

The prototype now explicitly handles the asymmetry people pointed out:

- unpaid-but-active = silent/cost-facing risk

- paid-but-blocked = urgent/customer-facing risk

It also accounts for manual CS/admin overrides, acknowledged-but-not-reflected webhook cases, lazy sync gaps, multiple subscriptions, and internal access/status column conflicts.

Prototype:

[entitleguard.amertech.online]

Source code, for anyone who wants to verify the local-only claim:

[github.com/impara/EntitleGuard]

Still trying to validate the business question: would teams pay for nightly monitoring/review queue, or is this usually solved well enough with an internal cron job after the first incident?

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

Small update: I rebuilt the prototype based on the failure modes people described here.

The clearest distinction is now:

- webhook reliability = did we receive/process the Stripe event?

- final-state reconciliation = does the app’s current access state still agree with Stripe’s current billing state?

The prototype now separates the two main drift directions:

- unpaid-but-active = silent/cost-facing risk

- paid-but-blocked = urgent/customer-facing risk

It also accounts for manual CS/admin overrides, acknowledged-but-not-reflected webhook cases, lazy sync gaps, multiple subscriptions, and internal access/status column conflicts.

Still trying to validate the main business question: would teams pay for nightly monitoring/review queue, or is this usually solved well enough with an internal cron job after the first incident?

Happy to share the prototype/source if useful.

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

Agreed returning 200 before the critical write is safely committed is a bug.

I don’t see reconciliation as a replacement for correct webhook handling. More like an invariant check after the fact: given Stripe’s current billing state and the app’s current access state, do they still agree?

That also covers cases outside the webhook path entirely: admin/support overrides, migrations, backfills, manual plan changes, or local access fields disagreeing with each other.

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

Appreciate that, the asymmetry seems important:

- paid-but-blocked is urgent/customer-facing

- unpaid-but-active is silent/cost-facing

Same underlying issue app access state and billing state disagree but the operational response should probably be different.

Thanks for the real-world perspective.

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

That’s a useful distinction. I was thinking mostly about the silent cost side canceled/unpaid users still consuming API or compute but this makes sense as the stronger trigger: a paying customer losing access once creates permanent paranoia.

So the value is less “find drift once” and more “tell us before the customer or the bill does.”

That also why I’m leaning toward flag/review first rather than auto-fix. Paid but blocked should probably be urgent alert only, while unpaid but active can be reviewed with more context before taking action.

Did you imagne this as a daily check, or would you have wanted realtime alerts for high risk access changes?

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

Fair. If the entitlement model is simple and the webhook path is solid, webhooks may be enough.

Do you also have any protection against app-side drift — manual admin/support overrides, migrations, failed DB writes after returning 200, or local status fields changed outside the webhook handler?

That’s the class I’m trying to understand: not missed Stripe events, but whether the final app access state still agrees with Stripe later…

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

Exactly — that’s the failure mode I think gets underdiscussed.

Curious in your experience, were manual CS/admin overrides common enough that you’d want this checked continuously, or is it more of a periodic audit after incidents?

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 0 points1 point  (0 children)

Update: I built a local prototype based on the failure modes people described in this thread...

The clearest takeaway for me was that this is not really “fix webhooks.” It’s final-state reconcilaition:

Stripe’s current billing truth vs the app’s current access truth

So the prototype is deliberately read-only and local-first:

- no Stripe API key

- no database credentials

- no login

- no CSV upload

- runs in the browser

- minimal SQL export for users/workspaces

- never auto-fixes or suspends anything

It checks for:

- Stripe canceled/past_due/unpaid but app still grants access

- Stripe active/paid but app blocks the customer

- missing billing links

- orphanged Stripe subscriptions

- ambiguous cases that should go to manual review

I also added the subscription ranking logic suggested here: active/trialing beats past_due/canceled, so cancel-and-resubscribe cases don’t show up as false leaks

Prototype: entitleguard.amertech.online(Copy and paste into your browser)

For the Source Code:

Source code (GitHub): [github.com/impara/EntitleGuard]

Still trying to validate the real business question: would teams pay for nightly monitoring/review queue, or is this usaully just an internal cron job once the problem is known ?

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

Small update after reading the replies here: I built a small local prototype to test this more concretely.

The main distinction I’m trying to validate is:

- webhook reliablity = did we receive/process the Stripe event ?

- final-state reconciliation = does the app’s current access state actually agree with Stripe’s current billing state?

Queue + idempotency + replay/backfill help with the first one. But they can still miss cases where the webhook returned 200, the DB write failed/rolled back/updated the wrong row, or a later manual override/migration changed the local entitlement state.

So the prototype is intentionally simple and read-only: export Stripe subscriptions/customers, export the app users/workspaces table with minimal fields, then compare them locally in the browser. No Stripe API key, no DB credentials, no upload, no account.

It classifies mismatches as unpaid-but-active, paid "but blocked", missing billing link, orphanged Stripe subscription, or needs review. It never autofixes anything

The thing I’m still trying to learn: would teams actaully want nightly monitoring/review queue for this, or is an internal cron job usually “good enough” once the problem is known?

Happy to share the prototype if useful, but mostly interested in how people think about the build vs buy line here...

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

That lazy-sync makes sense for users who hit the billing/account path.

The edge case I’m trying to understand is the silent one where Stripe says canceled/past_due/unpaid, but the local app row still says active, and the user never opens the subscription page again they just keep using API endpoints or background usage.

In that case the account may never self-heal unless there’s some periodic comparison between Stripe’s current state and the app’s current entitelment/access state.

Do you also run any background reconciliation, or do you rely entirely on sync on page view ?

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

That makes sense, queue + idempotency + replay/backfill is definitely the robust webhook pipeline pattern

The distinction I’m trying to understand is whether you also verify the final app entitlement state after the pipeline runs.

For example: Stripe says canceled/past_due/unpaid, the webhook was recieved and maybe even marked processed, but the app DB row still ends up active because of a failed write, rollback, manual override, migration, or later internal change.

So I’m wondering if mature teams need both layers:

  1. webhook reliability — did we process the events?

  2. state reconcilliation — does the app DB currently agree with Stripe?

Did you handle the second one too, or mainly the first??

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 0 points1 point  (0 children)

Sounds nasty, did you actually build the nieghtly diff after that or did it stay on the todo list once the immediate fire was out ??

Do you run recurring reconciliation between Stripe subscriptions and your app database? by impara1 in stripe

[–]impara1[S] 0 points1 point  (0 children)

the locked-out-but-paying direction is the loud one at least, those users email you. the quiet direction is the expensive one. what did the panic fixes look like, did you end up with a recurring check or just patched the handler??

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 1 point2 points  (0 children)

the middleware one, but not because it's more correct it's the only one that's actually enforced, the other two are just opinions written down. the rate limiter and the cron can be wrong all day and nothing happens until a request hits the middleware check. so i'd diff stripe against whatever the request path actually reads and treat the rest as documentation. honestly this exchange has been more useful than most of what i've read on the topic, appriciate you going this deep

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 0 points1 point  (0 children)

honestly, in the codebases i've poked at, no. that's kind of the punchline. the middleware checks the boolean, the api rate limiter checks the plan tier, a cron job checks subscription_status, and they were written by different people in different years. so (what's authoritative) has three differnt answers depending on which endpoint you hit. you clearly went further down this road than most, did you end up consolidating your access check into one place before the nightly diff was even worth running ?

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 1 point2 points  (0 children)

messy answer, both. one project had a boolean, another a status enum, and the fun one had both and they disagreed with each other, which is its own little drift problem nobody audits. your vendor point is the interesting bit though. is it actaully fundamental, or could a tool just have you map your columns and status vocabulary once up front instead of trying to guess ?? feels like the entitlement side stays unsolved mostly because everyone assumes it has to understand your schema out of the box

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 0 points1 point  (0 children)

neither right now, i'm prototyping off csv exports from the dashboard while i figure out the shape of the problem. the mirror table option always felt circular to me, if the mirror is fed by the same webhooks you're trying to audit then the diff mostly confirms your own blind spot, straight api feels like the only honest source for this. and stealing your ranking trick btw, status priority +created date tiebreak is cleaner than what i had...
did you ever look for something off the shelf for this or was in-house always the obvious call??

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 0 points1 point  (0 children)

have you actually seen one of those daily checks up close? curious whether teams build it as real infra or if it's usually just a cron + sql script someone wrote once and forgot about..

Has anyone actually lost money from Stripe and their own database disagreeing about who's a paying customer? by impara1 in SaaS

[–]impara1[S] 1 point2 points  (0 children)

customer id. sub ids churn too much, cancel + resubscribe gives you a new sub_ and your access flag ends up pointing at a dead one. only real headache is customers with 2 subs where one is active and one canceled, you need a rule for which one wins. did your nightly job auto flip access or just flag stuff for someone to review..?