The interesting question for a micro-SaaS is not “Which collection of free tiers costs the least?” It is: Can a customer sign in, use the product, pay, lose a connection, and still end up with the correct account state? F3Fundit already has a six-layer tool-and-price stack and a separate under-$50 stack. Those articles address product selection; this blueprint addresses the seams where apparently inexpensive tools meet.[1][2]

Draw one customer request and one payment event

Consider a small account-based reporting app. Its browser calls an application endpoint. The endpoint checks the user, authorizes access to that account’s reports, and reads or writes a database. For one plausible implementation, Cloudflare Workers serves the API, Supabase holds identities and Postgres data, and Stripe Checkout collects payment. This is an example boundary map, not a recommendation that those three vendors suit every workload. Workers charges along request and CPU dimensions; Supabase also exposes compute, database, storage, and egress dimensions. Check their current plan pages against your expected traffic and region rather than importing somebody else’s monthly total.[6][8]

Put an account or tenant identifier on every report row. If the browser talks directly to Supabase, enable and test row-level security on every exposed table: Supabase warns that an exposed table without RLS can be read or written by roles with grants. If the browser talks only to your API, that API still needs authorization checks. Neither an authenticated session nor hiding a table name is an account-isolation test. Keep elevated database credentials on the server, never in client code.[7]

Now draw the separate billing path. Your server creates a Checkout Session; Stripe redirects the customer to its payment flow; a signed webhook updates your application’s entitlement record. Do not grant access merely because a user returned to a success page. Stripe says webhook fulfillment is required for subscriptions and delayed payment methods, since the landing page may never load.[10]

Make the webhook boring—and recoverable

At the webhook endpoint, verify Stripe’s signature against the original request body, persist or enqueue only the event types you need, and acknowledge promptly. Stripe documents duplicate delivery and events arriving out of order. A unique Stripe event ID suppresses redelivery of that Event; it is not a unique business-operation key. Stripe also documents distinct Event objects for the same underlying object and event type: compare data.object.id together with event.type to recognize that case. Never infer a newer subscription state solely from webhook arrival order. Retrieve the current Stripe object when the event alone is insufficient, and define which authoritative state grants or removes access.[9]

For Checkout fulfillment, make the operation conditional on the Checkout Session ID and its verified payment/fulfillment state, not merely on the Event ID: Stripe warns the fulfillment function can be called repeatedly or concurrently for one Session. In your database, use a unique business-operation key (for example, checkout_session_id + fulfill) and a transaction with a uniqueness conflict or row lock so competing handlers cannot both commit that entitlement transition. For subscription changes, key the entitlement to its subscription ID and conditionally apply the intended transition against current authoritative state; do not treat two distinct Event IDs for the same transition as two grants, or a late older notification as a newer state. Preserve the Event-ID receipt record for delivery tracing, but coordinate it with the business-operation/entitlement transaction; an Event-ID ledger alone is insufficient. This is an implementation design to test, not a claim that Stripe or a database provides exactly-once payment or fulfillment effects.[9][10]

If the process dies before commit, a retry should still be able to apply the database change; if it dies after commit, another delivery should not apply the same business transition again. For external actions such as a welcome email, use an outbox and a provider-specific idempotency key when supported; otherwise reconcile an ambiguous send before retrying. A committed database transaction cannot atomically guarantee one remote email, refund, or payment effect. Inspect and reconcile ambiguous outcomes rather than claiming end-to-end exactly-once behavior.

Price the shape of usage, not a fictional bill

Make a worksheet with requests and CPU, database compute and disk, storage and egress, authentication activity, payment volume and methods, transactional email, logs, and backups. Record each vendor’s included usage, overage trigger, region, billing unit, and the month checked. Cloudflare’s Worker invocation, CPU and static-asset rules differ: static-asset requests are ordinarily free, but requests served from Workers Caching are billed at the Worker request rate, including static assets.[6] Supabase’s current pricing page includes database and egress quotas and notes that free projects pause after inactivity; free is not a service-level promise for an active production account.[8] Stripe publishes standard and custom payment pricing, so a zero setup-fee claim must not be mistaken for zero transaction cost or a universal rate.[12]

Cost is not just a vendor invoice. A single-machine deployment may lower one bill but transfer patching, backups, capacity planning, and outage response to you. A managed platform may save operator time but create egress or usage surprises. No architecture by itself establishes a universal customer capacity, response time, or dollar total. Size against measured usage after launch.

A launch-and-recovery checklist

  1. Sketch browser, API, identity, database, payment, webhook, email, analytics, logs, and backup destinations. Identify what customer data each receives.
  2. Try two test accounts: account A must not read or modify account B’s row via either browser or API. Check RLS and server authorization separately.[7]
  3. In Stripe’s test environment, redeliver one signed Event ID, send distinct Event IDs for the same Checkout Session/object-and-type business operation, invoke fulfillment concurrently for one Session, deliver relevant subscription events out of order, and interrupt the return-page visit. Inspect the final entitlement, Event-ID receipt records, business-operation uniqueness, and any ambiguous external sends; do not call the exercise “tested” until you run it.[9][10]
  4. Export a backup to an access-controlled, separate location and rehearse a restore into a non-production environment. Supabase recommends that free-tier projects export data and maintain off-site backups; a database’s existence is not a recovery plan.[11]
  5. Set alert thresholds for failed webhooks, overdue processing, authorization failures, storage growth, and restoration failure. Assign a person and a procedure for each alert.

The privacy boundary is broader than the database: a hosted auth, payment, email, logging, or analytics service may receive identifiers or customer content. Minimize what crosses each boundary; decide retention and access controls from actual configuration and agreements, not from a “privacy-first” adjective. This blueprint deliberately leaves taxes, legal duties, processor terms, and product-specific performance to the operator to verify.

Sources

Pin It on Pinterest