A stressed CTO holds an official envelope, laptop screen behind shows abstract financial data. The challenge of stripe tax multi jurisdiction saas.
Production engineering patternUpdated

Stripe Tax for Multi-Jurisdiction SaaS: US States and EU VAT

A pattern for selling SaaS across US states and EU VAT without inventing a tax-engine project from scratch — what Stripe Tax handles, what it does not, and the API shape that holds up at scale.

The problem

Nexus notice from a state DOR

The first time a SaaS founder hears the phrase "economic nexus" it is usually from a state Department of Revenue notice in the mail, three quarters after they crossed the threshold. The notice says the company should have been collecting California sales tax since the quarter their California revenue passed 500,000 dollars. It asks for back taxes, interest, and penalties on every sale into the state since that date. The company did not charge California customers tax for that whole period, so the back taxes come out of the company's margin — not the customer's wallet.

Thirty tax regimes across US and EU

That is the worst version of the problem. The more common version is quieter and more expensive in total. A SaaS that sells into 20 US states and 10 EU countries has 30 different tax regimes to track. Each US state has a different threshold (most are 100,000 dollars or 200 transactions over a rolling 12 months; California and Texas use 500,000 dollars). Each EU country has its own VAT rate (Germany 19 percent, France 20 percent, Hungary 27 percent, Luxembourg 17 percent). EU B2B sales need reverse-charge treatment if the buyer has a validated VAT ID, but the consumer next door pays full VAT. Some states tax SaaS, some do not, and some tax it only if accessed by an in-state user. Non-profits and resellers are exempt — but only after they hand you a certificate you have to file.

Build-vs-defer trap

The temptation is to build a tax engine. A few teams have tried. The result is a project that consumes a senior engineer for a quarter, ships a brittle rules table that lags every state's mid-year change, and still leaves the founder personally exposed because no one on the team is a CPA. The other temptation is to ignore the whole thing and "fix it when we get bigger." That works until the first nexus notice arrives, which is usually right before a fundraise when the company can least afford to discover a six-figure tax liability sitting in the data room.

Month-end reconciliation across three places

The hidden cost is reconciliation at month-end. If tax calculation lives in three different places — your billing code for US sales, a manual spreadsheet for EU VAT, a separate tool for the one country your CFO insisted on — every close becomes a multi-day exercise in matching numbers that no two systems agree on. Finance ends up with three versions of the same number and picks the one they trust most, which is usually the wrong one.

Two engineers collaborate, one at a laptop with abstract code, the other at a whiteboard with colorful shapes. Shows the integration of stripe tax multi jurisdiction saas.

What changes for your business

The pattern that holds up is to let Stripe Tax own calculation, monitoring, and reporting; let your code own the shape of the customer and product data Stripe Tax needs; and design the rest of your billing flow so it degrades cleanly when Stripe Tax cannot calculate (because the customer's address is incomplete, because your registration in that state is not active yet, because the customer is exempt). The three pieces are coverage, customer-side data, and graceful degradation.

Coverage via automatic_tax

Coverage means enabling automatic_tax[enabled]=true on every subscription, invoice, and Checkout session you create. The moment you set this flag, Stripe Tax calculates tax against the customer's location and the jurisdictions where you have an active registration in Stripe. If you have not registered in a jurisdiction yet, no tax is calculated there — which is correct behavior, because collecting tax without a registration is itself a problem.

import Stripe from "stripe";

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

export async function createSubscription(input: {
  customerId: string;
  priceId: string;
}) {
  return stripe.subscriptions.create({
    customer: input.customerId,
    items: [{ price: input.priceId }],
    automatic_tax: { enabled: true },
    payment_settings: {
      save_default_payment_method: "on_subscription",
    },
    expand: ["latest_invoice.confirmation_secret"],
  });
}

For Checkout sessions, the flag is identical, and Stripe Tax additionally collects the address fields it needs as part of the Checkout flow itself. That removes a class of bugs where a self-serve signup leaves the customer with a partial address that breaks tax calculation on the first invoice.

Customer-side data Stripe Tax needs

Customer-side data is where most of the engineering effort actually lands. Stripe Tax needs three things on the Customer: a complete address with country, state (US), and postal code; a tax_exempt status (none, exempt, or reverse); and a list of tax IDs (for EU VAT, US EIN, etc.). The first one is the one that breaks most often, because checkout forms collect partial addresses and self-serve flows accept just an email and a card.

export async function ensureCustomerForTax(input: {
  email: string;
  name: string;
  address: {
    line1: string;
    city: string;
    state: string;       // ISO-3166-2 subdivision code, e.g. "CA"
    postal_code: string;
    country: string;     // ISO-3166-1 alpha-2, e.g. "US"
  };
  euVatId?: string;      // optional — present means B2B EU
}) {
  const customer = await stripe.customers.create({
    email: input.email,
    name: input.name,
    address: input.address,
  });

  if (input.euVatId) {
    await stripe.customers.createTaxId(customer.id, {
      type: "eu_vat",
      value: input.euVatId,
    });
  }

  return customer;
}

VAT ID validation through VIES

When you create the EU VAT ID, Stripe submits it to VIES (the European Commission's VAT Information Exchange System) and stores the validation result on the tax_id object. The result is one of pending, verified, unverified, or unavailable. For reverse-charge treatment, Stripe needs the ID to be present — validation status affects whether Stripe records the verification, but the reverse-charge logic fires when a customer in one EU country has a tax ID for a different EU country (the cross-border B2B condition). For the most common case — a German SaaS selling to a French B2B customer with a validated FR VAT number — the invoice shows zero VAT and the reverse-charge note.

Graceful degradation on calculation failure

Graceful degradation means writing the rest of your billing code so a failed tax calculation does not block the charge or leave the customer in a bad state. The two failure modes that matter are automatic_tax.status: requires_location_inputs (the customer's address is too vague to map to a jurisdiction) and invoice.finalization_failed (Stripe Tax could not finalize the invoice because of a calculation error). The pattern is to listen for both, route the customer through an address-fix flow for the first, and alert a human for the second.

A calm finance lead smiles, looking at a clean, color-blocked dashboard on a monitor, reflecting control over stripe tax multi jurisdiction saas.

More on this

US economic nexus thresholds by state

Every state with a sales tax has its own remote-seller threshold post-Wayfair. The full list moves whenever a state legislates, but the shape is stable. Most states sit at 100,000 dollars in annual revenue or 200 transactions over a rolling 12 months. A handful are notably different.

| State | Revenue threshold | Transaction count | Lookback | |---|---|---|---| | California | 500,000 USD | none | preceding or current calendar year | | Texas | 500,000 USD | none | preceding 12 calendar months | | New York | 500,000 USD | 100 | preceding 4 sales tax quarters | | Florida | 100,000 USD | none | preceding calendar year | | Pennsylvania | 100,000 USD | none | preceding 12 months | | Washington | 100,000 USD | none | current or preceding calendar year | | Illinois | 100,000 USD | 200 | preceding 12 months | | Massachusetts | 100,000 USD | none | preceding or current calendar year | | Georgia | 100,000 USD | 200 | preceding or current calendar year | | Most other states | 100,000 USD | 200 | preceding or current calendar year |

Stripe Tax monitors these for you. The Dashboard shows a per-state view of where you stand against each threshold, with alerts when you cross. The threshold-monitoring page in Stripe surfaces obligations starting at 10,000 USD in yearly revenue, which is the floor below which monitoring is not yet warranted. The mechanics: Stripe sums your Stripe-processed sales (minus refunds) by customer location, compares against each jurisdiction's threshold, and surfaces a registration prompt when you are close or over. Some states (Arizona, Indiana, North Carolina noted in Stripe's documentation) include nontaxable sales in their threshold counts, which means a SaaS selling a non-taxable category in those states still needs to register and file an information return showing zero tax due.

When you cross a threshold, register with that state's DOR — directly, through Stripe's registration service (where supported), or through your accountant. Then add the registration in Stripe via the Tax Registrations API or the Locations tab in the Dashboard. The registration must be active in Stripe before tax will be calculated for that jurisdiction.

export async function addUsStateRegistration(state: string) {
  return stripe.tax.registrations.create({
    country: "US",
    country_options: {
      us: {
        state,                            // e.g. "CA"
        type: "state_sales_tax",
      },
    },
    active_from: "now",
  });
}

EU VAT and the reverse charge

EU VAT for digital services follows the destination principle — VAT is owed in the country where the customer is, not where you are. If you are a US SaaS selling to a German consumer, you owe German VAT at 19 percent. If you are a German SaaS selling to a French consumer, you owe French VAT at 20 percent. If you are either of those selling to a French B2B customer with a validated VAT ID, the buyer accounts for the VAT themselves under reverse charge and your invoice shows zero VAT.

Three things drive the calculation: the customer's country, the customer's tax_exempt status (reverse is the value Stripe uses for explicit reverse charge), and the presence of a validated tax ID. Most teams do not set tax_exempt to reverse explicitly — instead, they collect the VAT ID and let Stripe Tax derive reverse-charge treatment automatically when the cross-border B2B condition is met.

// On the customer create / update path:
await stripe.customers.createTaxId(customerId, {
  type: "eu_vat",
  value: "DE123456789",     // a valid German VAT ID
});

// On the invoice / subscription create path:
await stripe.subscriptions.create({
  customer: customerId,
  items: [{ price: priceId }],
  automatic_tax: { enabled: true },
});

That is all the code. Stripe Tax submits the VAT ID to VIES, marks the verification result on the tax_id object, and applies reverse charge on the next invoice if the cross-border B2B condition holds. The invoice line shows zero VAT and a note explaining the reverse charge. If VIES is down (it does go down — the EU operates the service and outages are not rare), Stripe records the verification as pending and retries.

For the B2C side, Stripe needs the customer's country to pick the right rate. The cleanest signal is a billing address; the fallback signal is the IP address geolocation passed via customer_details[tax][ip_address] on Checkout. Both are accepted, but a billing address is required to finalize a recurring invoice — the IP fallback works for the first preview but the subscription needs a real address before the first invoice closes.

The VAT MOSS / OSS / IOSS frameworks let you remit VAT for all EU countries through a single registration in one member state instead of registering in each one separately. Stripe Tax tracks VAT OSS threshold separately from each country's domestic threshold. For most non-EU SaaS the right answer is to register for the non-Union OSS scheme through an EU member state of your choice and remit through that single filing.

Exclusive vs inclusive pricing

Stripe lets you set tax behavior per Price: exclusive (tax adds to the displayed amount) or inclusive (tax is backed out of the displayed amount). The choice is a product question more than a tax question, and the wrong choice creates either confused customers or unhappy finance teams.

The exclusive pattern: list price 50 USD, US customer in California pays 50.00 plus tax. The Stripe-computed total at a 7.25 percent state rate is 53.63. The customer sees a subtotal line, a tax line, and a total. Most B2B SaaS sold to US customers uses this pattern because the buyer's procurement team expects to see the tax line separately.

The inclusive pattern: list price 50 EUR, German customer pays 50.00 total, of which 7.98 is VAT (19 percent backed out of 50). The customer sees only the total — the tax breakdown is on the invoice. EU consumer brands typically use this pattern because EU consumer protection rules expect the displayed price to be the price the customer pays at checkout.

The mixed pattern: you sell to both US and EU customers and pick one. The standard call is exclusive globally, because exclusive pricing in the EU is acceptable for B2B (the buyer expects to see VAT separately for their own VAT recovery) and because the inverse — inclusive pricing in the US — surprises customers who do not expect tax to be already-included.

The choice is per-Price, not per-Customer, so changing your mind later means creating new Prices and migrating active subscriptions. Pick once with finance, document the decision, and apply it consistently.

// Exclusive (most common for B2B SaaS):
await stripe.prices.create({
  product: productId,
  unit_amount: 5000,        // 50.00 in cents
  currency: "usd",
  tax_behavior: "exclusive",
  recurring: { interval: "month" },
});

// Inclusive (EU B2C pattern):
await stripe.prices.create({
  product: productId,
  unit_amount: 5000,        // 50.00 in cents, tax included
  currency: "eur",
  tax_behavior: "inclusive",
  recurring: { interval: "month" },
});

Exempt customers and certificates

Non-profits, government entities, and resellers are exempt from sales tax in most US states and from VAT in some EU contexts. Stripe handles this through Customer.tax_exempt, which takes three values: none (default), exempt (zero tax across all jurisdictions), and reverse (treat as reverse-charge in jurisdictions where it applies).

The operational pattern: a customer asks for tax-exempt status, your support workflow collects the certificate (a state-issued resale certificate, a 501(c)(3) letter, a government PO), an actual human reviews it, and only then does the customer record get flipped to exempt. Stripe explicitly does not validate the certificate — that responsibility is yours. The risk of flipping the flag without the certificate is that the state audits you, finds an unsupported exemption, and assesses the tax against you as the seller.

// After your support team has reviewed and stored the certificate:
await stripe.customers.update(customerId, {
  tax_exempt: "exempt",
});

// On future invoices for this customer, Stripe Tax returns tax of 0
// with taxability_reason: 'customer_exempt' on each line item.

Store the certificate itself (PDF, scan) in your CRM or document store along with the date of verification, the staff member who reviewed it, and the expiration date if the certificate has one. Many resale certificates are perpetual; many non-profit certificates expire. Build a reminder so they get re-collected before they lapse, because an expired certificate during an audit looks the same as no certificate at all.

Returns, refunds, and credit notes

A full refund reverses both the charge and the tax. Stripe Tax reflects this in its reports as a negative line in the period the refund happened — not the period the original sale happened. That matters because your filing for the original period stays the same (you collected the tax, you remitted it), and the reduction shows up in the next filing as a refund deduction.

A partial refund prorates the tax. If you refund 50 percent of the subtotal, you refund 50 percent of the tax. Stripe handles the proration automatically when you create the refund through the standard API — you do not specify a tax amount.

Credit notes (stripe.creditNotes.create) are the right primitive for correcting an invoice before or after payment. A credit note before payment reduces the amount due and recalculates tax against the new amount. A credit note after payment refunds the corresponding tax along with the corrected amount.

The reporting consequence: your Stripe Tax export at month end already nets refunds and credit notes against the gross collected. The number you file is the netted number. Do not double-count by exporting gross from one report and refunds from another — use the consolidated tax report Stripe produces and file from that single number.

Common failure modes

The first sharp edge is the customer with a partial address. Self-serve signup collects an email and a card. Stripe Tax cannot determine the jurisdiction without at minimum country and postal code, and for US it needs the state. The first invoice fails with automatic_tax.status: requires_location_inputs. The fix is to require a complete address during signup or to gate the first paid action behind an address-collection screen.

The second sharp edge is registering in a jurisdiction inside Stripe before you are actually registered with that DOR. Stripe will calculate and collect tax based on what is in the Locations tab, regardless of whether the underlying registration is real. If you flip the switch in Stripe but the state has no record of your registration, you are collecting tax under false pretenses — which is its own legal exposure. The order is: register with the DOR, receive the registration confirmation, then add it in Stripe.

The third sharp edge is the customer who provides an EU VAT ID that VIES marks as unverified. The most common cause is a typo. The second most common is that the customer's VAT registration lapsed. The third is that VIES is temporarily down. Build the customer experience around all three: show the verification status, let the customer retry, and treat a pending status as a soft state that may resolve on its own. Do not silently apply reverse charge against an unverified VAT ID — the safe default is to charge VAT and let the customer claim it back if their registration was valid.

The fourth sharp edge is the state that taxes SaaS sometimes. New York taxes some SaaS, exempts others, and the line depends on whether the customer is using the software to access information versus performing a data-processing service. Iowa, Pennsylvania, and Texas all have nuanced SaaS taxability rules. The product tax code you set on each Price tells Stripe Tax which rule to apply — using the wrong code means the tax is wrong in the affected states. Pick the closest matching tax code for your product type and have an accountant confirm it for the states you sell into most.

The fifth sharp edge is the mid-year rate change. EU member states change VAT rates more often than US states change sales tax. Stripe updates the rate tables on its side, but if you have hardcoded a rate anywhere in your own code (for display, for a customer-facing estimator, for a CSV export), you will be wrong from the date the change took effect. The pattern: query Stripe for the rate at the moment you need to display one, do not cache it across days.

What to watch in your own implementation

Open the Customer object for your top 20 accounts and check three fields each: address.country, address.state (if US), address.postal_code. If any are missing, that customer's next invoice will fail tax calculation. Fix those by reaching out for the data — it is faster than waiting for the invoice to fail.

Then check the Tax Registrations list against the states where you have customers. If you have substantial revenue from a state and no active registration in Stripe, run the threshold-monitoring report to see whether you crossed without noticing. If you crossed, register with the DOR first, then add the registration in Stripe.

Then look at your Price catalog and confirm tax_behavior is set consistently. A mixed catalog (some Prices exclusive, some inclusive) creates inconsistent customer experiences and breaks consolidated reporting. If you find a mix, migrate to one or the other and deprecate the off-pattern Prices.

Then look at your webhook handler for invoice.finalization_failed. If you do not handle this event, a tax-calculation failure on a recurring invoice silently fails and no one sees it until the customer notices their subscription did not renew. The handler should at minimum alert a human and capture the failure reason from the invoice object.

Finally, run a one-month tax report from Stripe and compare the totals to what your books recorded as tax-collected revenue. They should match within rounding. Drift in either direction means tax is being calculated somewhere your accounting system does not see, or recorded somewhere Stripe does not see. The single-source-of-truth pattern is that Stripe is the source of truth for tax-collected and your accounting system records from the Stripe export — not the other way around.

At BFEAI we run Stripe Tax across US states and the EU VAT framework on the credit-purchase side of the business, with the customer-side data model described above. The pattern surfaces threshold-crossing alerts in time to register before they become problems, keeps EU B2B reverse-charge automatic so finance does not handle each invoice by hand, and produces a single month-end tax report that finance files from directly. The total ongoing engineering cost of the pattern is approximately zero — Stripe Tax does the work, and the only code that has to exist on your side is the customer-data shape and the graceful-degradation paths. That is the right tradeoff for a SaaS that wants to sell into all 50 states and the EU without hiring an in-house tax team.

Outcomes you should expect

What this delivers

  • Tax registration obligations surface from threshold monitoring before a state DOR notice arrives, so registration happens on your timeline instead of theirs.
  • EU B2B customers with a validated VAT ID see a zero-tax invoice with reverse-charge wording, while EU B2C customers see the destination-country VAT line.
  • US customers across multiple states see the correct combined state-and-local rate at checkout because location is detected and matched against active registrations.
  • Exempt customers (non-profits, government, resellers) are flagged once on the Customer object and stay zero-rated across every future invoice without per-charge overrides.
  • Finance closes the month against a single Stripe tax report instead of reconciling separate spreadsheets per jurisdiction.

Primary sources

By the numbers

  • Stripe Tax alerts you to potential tax obligations (known as economic nexus in the US) when your business reaches 10,000 USD in yearly revenue.

    Source ↗

  • Stripe Tax tracks your Stripe-processed sales (minus refunds) based on each customer's location and compares these sales against local tax registration thresholds.

    Source ↗

  • California's threshold for remote sellers is the total combined sales of tangible personal property for delivery in California by the retailer and all persons related to the retailer exceeding 500,000 dollars.

    Source ↗

  • Remote sellers with total Texas revenue of less than 500,000 dollars in the preceding twelve calendar months are not required to obtain a tax permit or collect, report and remit state and local use tax.

    Source ↗

  • Stripe automatically validates all European Value-Added-Tax (EU VAT) numbers with the European Commission's VAT Information Exchange System (VIES).

    Source ↗

  • Stripe Tax automatically applies the reverse charge based on the presence of a tax ID and the jurisdictions involved in the transaction.

    Source ↗

  • Stripe Tax doesn't validate required documentation for supporting an exemption, such as customer exemption certificates. You're responsible for determining and fulfilling any obligation to validate your customer's exempt status.

    Source ↗

Live in production today

The same engineering, shipped in production at BFEAI.

I'm co-founder & CTO of Be Found Everywhere (BFEAI), a 7-app AI SaaS platform running today. The work I deliver for clients is the work I do every week on my own platform.

7

Production apps

200K+

Keywords generated

1,500+

AI scans run

7,000+

Sites automated

Common questions

What buyers ask before reaching out

Do I need to register for sales tax in every US state on day one?

No. Each state defines its own economic nexus threshold, and you typically owe registration only after you cross that state's revenue or transaction count over the lookback window. Most states sit at 100,000 dollars or 200 transactions in a 12-month period. California and Texas use 500,000 dollars with no transaction count. The right pattern is to let Stripe Tax monitor sales per state, then register where you cross — not to pre-register everywhere.

What does 'economic nexus' actually mean for a SaaS founder?

It means a state can require you to collect and remit sales tax based on your revenue from that state alone, with no physical office, employee, or server inside it. After the 2018 South Dakota v. Wayfair Supreme Court decision, every state with a sales tax now has its own remote-seller threshold. Crossing it creates a filing obligation even without any physical presence in the state. SaaS is taxable in some states and exempt in others, so the threshold and the taxability of your product are two separate questions.

How does VAT reverse charge work for B2B EU customers?

When you sell a digital service to a VAT-registered business in another EU country, the buyer accounts for the VAT on their own return instead of paying it to you. Stripe Tax handles this automatically when a customer provides a valid EU VAT ID that Stripe verifies through VIES. The invoice shows zero VAT and the reverse-charge note. For B2C customers without a VAT ID, you charge the destination country's VAT rate.

Should I use exclusive or inclusive pricing for my SaaS?

Most US-headquartered SaaS uses exclusive pricing — the listed price is pre-tax and tax shows as a separate line. Most EU-headquartered B2C SaaS uses inclusive pricing because EU consumers expect the displayed price to be the price they pay. If you sell to both US and EU customers, the standard pattern is exclusive pricing globally with Stripe Tax adding the destination tax on top. Inclusive pricing means a French customer and a Spanish customer pay the same total but different subtotals after tax is backed out.

What happens if a customer claims tax exemption?

You set Customer.tax_exempt to 'exempt' on the Stripe customer, and from that point every invoice for that customer is zero-rated regardless of jurisdiction. Stripe Tax does not validate the exemption certificate itself — that responsibility sits with you. The operational pattern is to require the certificate via your support workflow, store the file, then flip the flag on the customer record only after the certificate is reviewed.

How do refunds and credit notes affect my tax filings?

A refund reduces the tax you collected, and Stripe reflects this in its tax reports as a negative line for the period the refund happened, not the period the original charge happened. For credit notes that correct invoice errors before the customer pays, the tax is recalculated against the new amount. For partial refunds, the tax is refunded proportionally. The reports you export at month end already net these out, so you file the net number rather than the gross.

Does Stripe file my returns or just calculate the tax?

Stripe offers US filing from the Dashboard powered by TaxJar, and global filing via partner integrations including Taxually, TaxJar, HOST, and Marosa. Stripe Tax itself calculates the tax, monitors thresholds, and produces filing-ready reports. Filing is a separate add-on or a separate vendor depending on the country. Many SaaS teams hand the Stripe reports to their existing accountant for filing rather than enabling the filing service.

What invoice display requirements does Stripe Tax handle automatically?

Stripe Tax line items include the jurisdiction, the tax type (sales tax, VAT, GST), and the rate. EU invoices include the seller's VAT number and the customer's VAT number when present, plus the reverse-charge notice when applicable. US invoices show state and local components combined as a single sales tax line by default. If your jurisdiction requires a specific invoice format your accountant flags, you can override the invoice template fields or insert a custom footer.

Ready to see if this is a fit?

A 15-minute call. No deck, no slides. We talk about what you're shipping and where engineering is the bottleneck. Either way, you walk away with a senior engineer's read on your situation.