<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chethan S</title>
    <description>The latest articles on DEV Community by Chethan S (@chethan_s_14126d37b9e35c2).</description>
    <link>https://dev.to/chethan_s_14126d37b9e35c2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4141461%2F06a00684-8442-478b-8dd3-43d1bf60e10a.png</url>
      <title>DEV Community: Chethan S</title>
      <link>https://dev.to/chethan_s_14126d37b9e35c2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chethan_s_14126d37b9e35c2"/>
    <language>en</language>
    <item>
      <title>3 Stripe/Express bugs that don't show up until production</title>
      <dc:creator>Chethan S</dc:creator>
      <pubDate>Thu, 24 Sep 2026 14:45:43 +0000</pubDate>
      <link>https://dev.to/chethan_s_14126d37b9e35c2/3-stripeexpress-bugs-that-dont-show-up-until-production-1002</link>
      <guid>https://dev.to/chethan_s_14126d37b9e35c2/3-stripeexpress-bugs-that-dont-show-up-until-production-1002</guid>
      <description>&lt;p&gt;I rebuild the same SaaS backend basics for every side project — auth, multi-tenancy, RBAC, Stripe billing — and always make at least one subtle mistake. So I built it once, properly: Node.js/TypeScript/Express/PostgreSQL/Prisma, boilerplate, tested against real requests. Here are three bugs from the process that a tutorial won't warn you about.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Webhook signature verification fails silently — because of middleware order&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stripe signs the raw request body. If express.json() runs before your webhook route, req.body is already a parsed object by the time you try to verify it — signature check fails every time, with an error that gives no hint it's an ordering problem.&lt;/p&gt;

&lt;p&gt;// Must come before app.use(express.json())&lt;br&gt;
app.post('/api/billing/webhook', express.raw({ type: 'application/json' }), handleStripeWebhook);&lt;br&gt;
app.use(express.json());&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stripe will redeliver events — without idempotency you'll double-process a signup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stripe explicitly documents at-least-once delivery. Guard it with an event-id table:&lt;/p&gt;

&lt;p&gt;const seen = await prisma.processedWebhookEvent.findUnique({ where: { id: event.id } });&lt;br&gt;
if (seen) return res.status(200).json({ received: true, duplicate: true });&lt;/p&gt;

&lt;p&gt;// ...handle the event...&lt;/p&gt;

&lt;p&gt;// Record success AFTER processing — so a mid-handler crash gets safely retried&lt;br&gt;
await prisma.processedWebhookEvent.create({ data: { id: event.id, type: event.type } });&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stripe's status enum outgrows your database enum&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Map active/trialing/canceled etc. 1:1 to a DB enum and it works — until Stripe adds a status you didn't model (e.g. paused), and the write throws. Use an allowlist with a logged fallback instead of a hard crash:&lt;/p&gt;

&lt;p&gt;const KNOWN = new Set(['trialing', 'active', 'past_due', 'canceled', 'incomplete', 'incomplete_expired', 'unpaid']);&lt;br&gt;
function toPrismaStatus(status: Stripe.Subscription.Status): SubscriptionStatus {&lt;br&gt;
  if (KNOWN.has(status)) return status as SubscriptionStatus;&lt;br&gt;
  console.warn(&lt;code&gt;Unrecognized Stripe status "${status}" — falling back to 'past_due'.&lt;/code&gt;);&lt;br&gt;
  return 'past_due' as SubscriptionStatus;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Not a full fix — extend the enum when you adopt the feature — but it turns a silent break into a visible warning.&lt;/p&gt;

&lt;p&gt;What's actually verified&lt;/p&gt;

&lt;p&gt;Auth, multi-tenancy, and RBAC are live-tested against a running server (signup, tenant isolation, token rotation, role checks — all real HTTP requests). Stripe billing follows the practices above and is code-reviewed, but not live-tested end-to-end — India's Stripe onboarding is invite-only right now. Flagging that here, same as on the listing.&lt;/p&gt;

&lt;p&gt;If you want it&lt;/p&gt;

&lt;p&gt;Full source, MIT-style license, setup + troubleshooting README: &lt;a href="https://schethan.gumroad.com/l/saas-backend-starter" rel="noopener noreferrer"&gt;https://schethan.gumroad.com/l/saas-backend-starter&lt;/a&gt; — $49, or $29 with code LAUNCH20 for the first 20.&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>stripe</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
