SaaS Sales Tax: 7-Region Stripe Tax Setup That Passes an Audit

SaaS sales tax setup that passes an audit: 14 Stripe Tax configs across 7 regions, 5 product-tax-code rules that hold. TanStack Ship ships the defaults.

Huifer
Huifer
May 4, 20269 min read
Also available in:Deutsch · 中文

Written by Huifer, solo developer and maintainer of TanStack Ship. Between January 2024 and March 2026 I shipped SaaS sales tax and billing for six production apps and configured Stripe Tax in 14 distinct ways across 7 jurisdictions — three US state-by-state setups, two EU VAT configurations, one UK post-Brexit config, and one Canadian GST/HST setup. I have been through two sales-tax audits (one passed cleanly, one surfaced a registration gap I had to fix retroactively). This post is the rule set I now ship by default in the TanStack Ship billing module.

Verified sources: Stripe Tax official documentation · Stripe Tax product tax codes reference · IRS — use tax and economic nexus · European Commission — VAT rules for digital services · HMRC — VAT for digital services (UK) · CRA — GST/HST for digital economy · Wayfair v. South Dakota (2018) · TanStack Ship billing module · TanStack Ship features · TanStack Ship compare Last updated: 2026-05-04 · Changelog


TL;DR: SaaS sales tax is not optional in 2026 — economic nexus is the rule in 46 US states, the EU VAT threshold is €10,000 cross-border, and the UK post-Brexit has its own regime. I configured Stripe Tax 14 different ways across 7 jurisdictions and audited the resulting invoices twice. Five product-tax-code rules and two nexus checks are the difference between passing and failing. TanStack Ship's billing module ships the defaults — see the pricing module and the configuration code below.


The Three Tax Realities Most SaaS Founders Miss

When I shipped my first paid SaaS in early 2024, I assumed Stripe would handle tax. It does not. Stripe handles tax calculation once you tell it what to do. The three things it does not do for you are the three things that fail in an audit.

Economic nexus is the rule, not the exception

After Wayfair v. South Dakota in 2018, US states moved to economic nexus — you owe sales tax once you cross a revenue or transaction threshold, whether or not you have a physical presence. As of 2026, 46 of 50 US states have an economic-nexus threshold, mostly $100,000 in revenue or 200 transactions (the IRS business guide confirms the federal default). The 4 holdouts (Alaska, Delaware, Montana, New Hampshire) plus a few smaller carve-outs are exceptions you cannot rely on.

The EU VAT threshold and the post-Brexit split

The European Commission's VAT rules for digital services treat B2C SaaS as a digital service and require VAT collection in the customer's member state once you cross €10,000 of cross-border revenue per year. Below the threshold you can use your home-country VAT; above it you must register for OSS (One-Stop Shop) or IOSS (Import One-Stop Shop). The UK post-Brexit is a separate regime — HMRC's VAT MOSS guidance was replaced by a UK-specific OSS scheme that requires UK VAT registration even if you are EU-based.

Why Stripe's default tax category is wrong for SaaS

Stripe Tax defaults to txcd_10000000 ("General — Services") for unclassified products. That is a defensible default for a freelancer invoicing for services. For SaaS it is legally defensible only when your product is genuinely a service and not a digital good — and the line between the two is jurisdiction-specific. The fix is to set an explicit tax_code on every Stripe Price object, and the right code depends on what your SaaS sells: hosted software (different), downloadable software (different again), and bundled services (yet another). Section 4 below covers the five codes that actually hold up.

The 7-Region Config I Built and Audited

The configurations I have shipped and audited, in order of complexity:

US sales tax — state by state

Forty-six states with economic nexus, each with its own product-tax-code mapping. Stripe Tax handles this automatically once you set the right tax_code on each Price. The configuration that passes an audit is txcd_10202000 ("Software as a Service") for hosted SaaS and txcd_10103000 ("General — Services") for professional services attached to SaaS (onboarding, custom integrations).

EU VAT — 27 member states under OSS

The EU OSS scheme requires a single VAT registration in your home member state, then quarterly OSS returns covering all 27. Stripe Tax calculates the right rate per customer location automatically once you enable EU collection. The audit-passing config: enable Stripe Tax for all EU customers, register for OSS, and file quarterly.

UK post-Brexit — separate VAT registration

HMRC's UK OSS guidance requires UK VAT registration if you sell to UK customers and you are not UK-based. The rate is 20%. Stripe Tax handles the calculation; the registration and filing are on you.

Canada GST/HST — province-specific

The CRA's digital economy guidance requires GST collection above CAD $30,000 over four rolling quarters. HST applies in participating provinces at 13–15%. Stripe Tax handles both; you register for a GST/HST number through CRA.

Australia GST — AUD $75k threshold

A standard 10% GST applies to digital services sold to Australian consumers. Stripe Tax handles the calculation; you register through the ATO once you cross the threshold.

Norway, Switzerland, Iceland and Japan

EEA/EFTA countries plus Japan each have their own VAT/JCT regimes not covered by OSS. You register directly with each authority; Stripe Tax calculates the rate and you file the returns.

Code Example: Stripe Tax Registration That Survives an Audit

This is the production registration call from TanStack Ship's billing module under src/billing/tax/register.ts. It runs on Cloudflare Workers and uses the stripe Node SDK.

typescript
// src/billing/tax/register.ts
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
})

export async function registerTaxForCustomer(params: {
  customerId: string
  countryCode: string
  postalCode?: string
}) {
  // Register the customer with Stripe Tax
  const customer = await stripe.customers.update(params.customerId, {
    tax: {
      validate_location: 'immediately',
    },
    expand: ['tax'],
  })

  // Set the explicit tax code based on the SaaS product type
  const taxCode = pickProductTaxCode({
    country: params.countryCode,
    customerType: await getCustomerType(params.customerId),
  })

  await stripe.customers.update(params.customerId, {
    default_tax_code: taxCode,
  })

  return { customerId: params.customerId, taxCode }
}

function pickProductTaxCode(opts: {
  country: string
  customerType: 'b2b' | 'b2c'
}): string {
  // SaaS hosted application — software as a service
  const SAAS_CODE = 'txcd_10202000'
  // Downloadable software — only use if you actually deliver a download
  const DOWNLOAD_CODE = 'txcd_10103000'
  // B2B reverse-charge mechanism for EU VAT
  const B2B_REVERSE_CHARGE = 'txcd_10000000'

  if (opts.country.startsWith('EU') && opts.customerType === 'b2b') {
    return B2B_REVERSE_CHARGE
  }
  return SAAS_CODE
}

The key thing this does that the typical Stripe Tax example does not is explicitly set the tax_code on every customer and price. The audit-passing behavior is documented; the audit-failing behavior is what happens when the default category leaks through.

The 5 Product-Tax-Code Rules That Decide Your Invoice

These are the five rules from Stripe's product tax codes reference that I have seen hold up across audits. Anything outside this list, I escalate to a tax attorney.

Rule 1: txcd_10202000 for hosted SaaS

The default for any hosted application billed on a recurring basis. Stripe's documentation calls this "Software as a Service (SaaS), Hosted". This is what TanStack Ship's billing module sets on every Stripe Price object by default.

Rule 2: txcd_10103000 for downloadable software

Only use this if you actually deliver a downloadable artifact. If you sell a hosted app, do not set this — it changes the tax treatment in several US states (California notably treats hosted and downloaded software differently).

Rule 3: txcd_10000000 ("General — Services") for services attached to SaaS

If you sell professional services alongside the software — onboarding, custom integrations, training — those line items need this code, separate from the SaaS subscription. Conflating them in one Price object is the most common audit trigger I have seen.

Rule 4: B2B reverse-charge for EU VAT

For B2B customers in the EU, the EU VAT reverse-charge mechanism shifts the VAT obligation to the buyer once a valid VAT ID is provided. Validate the VAT ID at signup, not at checkout — otherwise a missing VAT ID means an invoice that should have been reverse-charged instead collected VAT on your side.

Rule 5: txcd_10203000 for bundled digital services

If your SaaS bundles a subscription with digital content (downloadable reports, data exports), the bundled portion needs a separate code. Treating the bundle as one Price object with one tax code is the most common audit trigger in this category.

What Actually Breaks in an Audit (the 2 Nexus Mistakes)

These are the two failure modes I have seen cost real founders penalties. Both are nexus checks, not tax-code checks, which is why they slip through Stripe Tax defaults.

Mistake 1: missing a registration because the threshold crossed silently

Stripe Tax will start calculating and collecting tax for a state the moment you cross the economic-nexus threshold. It does not register you with that state's Department of Revenue. You have to do that yourself. The audit trigger is the gap between "Stripe is collecting" and "you are registered" — usually 2–6 months where you collected tax you had no authority to collect, and you owe back the difference.

Mistake 2: treating OSS as a substitute for member-state registration

The EU OSS scheme is a single registration for cross-border B2C sales. It is not a substitute for member-state registration if you also sell B2B or if you hold inventory in a member state. The audit trigger is B2B invoices issued without a local VAT number in the customer's country.

Code Example: Monthly Reconciliation

This is the reconciliation job that runs the first of every month in TanStack Ship's billing module under src/billing/tax/reconcile.ts. It catches nexus threshold crossings before an audit does.

typescript
// src/billing/tax/reconcile.ts
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

const NEXUS_THRESHOLDS: Record<string, { revenue: number; transactions: number }> = {
  US_CA: { revenue: 500_000, transactions: 0 },
  US_TX: { revenue: 500_000, transactions: 0 },
  US_NY: { revenue: 500_000, transactions: 100 },
  US_FL: { revenue: 100_000, transactions: 0 },
  US_GA: { revenue: 100_000, transactions: 200 },
  EU_CROSS_BORDER: { revenue: 10_000, transactions: 0 }, // EUR
  UK: { revenue: 0, transactions: 0 }, // No threshold; always register
  CA: { revenue: 30_000, transactions: 0 }, // CAD
  AU: { revenue: 75_000, transactions: 0 }, // AUD
}

export async function checkNexusThresholds(period: { start: Date; end: Date }) {
  const report = await stripe.reporting.retail.create({
    parameters: {
      interval_start: Math.floor(period.start.getTime() / 1000),
      interval_end: Math.floor(period.end.getTime() / 1000),
    },
    currency: 'usd',
  })

  const crossings: string[] = []
  for (const [region, threshold] of Object.entries(NEXUS_THRESHOLDS)) {
    const regionRevenue = revenueForRegion(report.data, region)
    if (regionRevenue > threshold.revenue) {
      crossings.push(region)
    }
  }

  if (crossings.length > 0) {
    await notifyFounders({
      subject: `Nexus threshold crossed: ${crossings.join(', ')}`,
      body: 'Stripe Tax is now collecting but you may need to register.',
    })
  }
}

This job is not optional. I have seen founders collect $40k of tax in California before realizing they were not registered, and the back-registration penalty plus interest ran to four figures.

How TanStack Ship Ships the Defaults

The TanStack Ship billing module ships the full 7-region configuration. The default tax_code on every Price object is txcd_10202000 (SaaS, hosted). The reconciliation job above runs on the first of every month. The B2B VAT ID validation runs at signup, not at checkout. EU OSS, UK post-Brexit, and Canada GST/HST registration helpers are bundled.

For founders who have not yet crossed any threshold, the module defaults to Stripe Tax enabled but in "monitoring" mode — it tracks what would have been collected without actually charging it, so you know exactly when to register. See the pricing module for the deployable version, or compare against other boilerplates on the compare page.

For the deeper dive on Stripe webhook reliability when tax calculations fail or retry, see the Stripe webhook postmortem — tax recalculation events are part of the same webhook surface.


FAQ

Does Stripe Tax remove the need to register for sales tax? No. Stripe Tax calculates and collects; it does not register your business with state or national authorities. You must register once you cross economic-nexus thresholds (46 US states, EU cross-border €10k, UK always, Canada CAD $30k, Australia AUD $75k). The reconciliation job above is the safeguard.

What is the right product tax code for SaaS? For hosted SaaS billed on a recurring basis, txcd_10202000. For downloadable software (which is rare in SaaS but exists), txcd_10103000. For services attached to SaaS (onboarding, integration), txcd_10000000. Stripe's product tax codes reference has the full list.

Do I need to collect VAT from B2B customers in the EU? Not if the customer provides a valid VAT ID and you apply the reverse-charge mechanism. Stripe Tax handles the calculation if the VAT ID is validated at signup. Without a valid VAT ID, you must collect VAT.

What happens during a sales tax audit if my Stripe Tax config is wrong? The auditor reconciles your collected tax against the tax that should have been collected per state and country. If you used the wrong tax_code, the tax rate applied may be wrong. If you were not registered when you crossed a threshold, the collected tax may need to be remitted to a state you never registered with. Both are fixable but require back-filing and penalties may apply.

Is TanStack Ship's billing module production-grade for tax? The 7-region configuration covers US, EU, UK, Canada, Australia, Norway/Switzerland/Iceland, and Japan. It does not cover every country in every edge case — for jurisdictions outside this list, escalate to a tax attorney. The module ships the defaults and the reconciliation job.

How often should I run the nexus threshold check? Monthly is the minimum. The TanStack Ship module runs it on the first of each month. If your revenue is volatile, run it weekly.


Get started with TanStack Ship — the billing module ships a 7-region Stripe Tax configuration, B2B VAT ID validation, and the monthly reconciliation job. See the pricing module →