Your first paying customer in the UK signs up on a Tuesday. That afternoon, you owe HMRC. Not next quarter, not “once you cross a threshold”, that afternoon, because the UK has charged VAT on digital services from the first pound since 2015. Germany works the same way for any EU customer. Most US states differ in shape but not in spirit: cross a state’s economic nexus threshold (sounds more sci-fi than it is, really just a sales-volume or transaction-count line that triggers a tax obligation) and they expect collection retroactive to the day you crossed.
Tax compliance doesn’t ship features, doesn’t move retention, and does ship audits. Most API teams ignore it until an accountant panics, or they bolt on a separate tax engine and a webhook to keep prices in sync.
Zuplo’s monetization stack uses Stripe Tax directly from the billing profile, so the same subscription that bills the customer also calculates, collects, and lines up the VAT or sales tax for remittance. No second engine, no sync job, no separate invoicing pipeline.
- You're charging for API access through Zuplo's monetization and Stripe
- Your first non-domestic customer is about to sign up, or has already
- Your finance team has asked where the VAT line on the invoice is going to come from
Why API tax is the work nobody wants
Tax on a digital subscription is a multi-axis problem.
Where you have to register. UK and EU charge VAT from the first sale to a local consumer. The EU’s One-Stop-Shop (OSS) lets you remit for all 27 from one return, but you still have to register somewhere. US economic nexus thresholds vary by state (commonly $100k in sales or 200 transactions a year), and crossing one means collecting from day one, often retroactively.
Who you’re charging. A B2C sale to a French consumer takes French VAT. A B2B sale to a French business with a valid VAT number takes the reverse-charge mechanism instead: zero VAT charged, the buyer accounts for it on their own return, and your invoice has to state that the reverse charge applies.
How prices are quoted. UK and EU custom is tax-inclusive ($9.99 includes VAT). US custom is tax-exclusive ($9.99 plus tax). The same plan needs both behaviours depending on where the customer logs in from.
What category your product is. Stripe Tax categorises every product, and the wrong code calculates the wrong rate. Stripe publishes the full list at docs.stripe.com/tax/tax-codes.
The bolt-on path is a separate tax engine (Avalara, TaxJar, Stripe Tax called from your own service) plus a sync job to keep prices, registrations, and invoice line items aligned. Three systems with three deploy pipelines for a feature that returns no signups.
What Stripe Tax does
Stripe Tax sits inside Stripe and handles the parts that don’t change per customer:
- Tracks your nexus and registrations across countries and US states.
- Resolves the customer’s tax location from address, IP, and payment method.
- Calculates the correct rate at invoice time using the product’s tax code.
- Returns line items the invoice can render in the customer’s expected format (inclusive or exclusive).
- Produces the reports you need to file and remit.
It doesn’t decide whether you should be registered somewhere. That’s your accountant’s call. Once the registration exists, calculation, collection, and reporting flow through every invoice without you wiring it up per plan.
Enable Stripe Tax in your billing profile
Zuplo exposes monetization config through its metering API. Your billing profile is the object on that API that holds plan billing, invoicing, and tax settings. Tax collection is a flag on that profile, plus your supplier country. One PUT once your Stripe registrations are in place, and the next invoice carries the right tax line.
Prerequisites
This post assumes you already have a monetized API running in Zuplo. If you don’t, the monetization quickstart covers that setup; come back here once your billing profile exists.
Add tax registrations in Stripe for every country where you’ve registered to collect. Stripe Tax can’t calculate for a jurisdiction it doesn’t know you’re registered in. Registrations live at dashboard.stripe.com/tax/registrations, keyed by your local registration number.
You’ll also need two values from your Zuplo project to call the metering API:
ZUPLO_BUCKET_ID, your monetization bucket. Find it under Services → Monetization Service → Bucket Details in the Zuplo portal. It looks likebckt_g1YMyT8DM90S0iZNmVMJiFohe0sWGhxF.ZUPLO_API_KEY, created in project settings. Treat it like any other server-side secret.

Fetch your billing profile
The metering API expects the entire billing profile back on PUT, not a partial diff. Two GETs: one to find the ID, one to write the full body to a file you’ll edit.
The response has an items array. Save the id as BILLING_PROFILE_ID, then:
Edit the tax fields
Open billing-profile.json and update three parts. The supplier country is the
one you’re collecting tax from (your company’s country, ISO 3166-1 alpha-2).
workflow.tax is the on-switch. invoicing.defaultTaxConfig sets the tax
behaviour and product tax code. Leave every other field from the GET response
exactly as-is:
txcd_10000000 is Stripe’s “General - Electronically Supplied Services” code,
the broadest sensible default for a metered API. enforced: false puts you in
best-effort mode, which is the right starting choice (more on that below).
behavior: "exclusive" adds tax on top of the listed price ($9.99 plan + 20%
VAT charges $11.99); switch to "inclusive" if your pricing page already shows
tax-included prices, which is the European default.
Pro tip:
Zuplo’s tax-collection doc uses txcd_10000000 in its example and
shorthand-labels it “SaaS”, but Stripe’s own catalog reserves the SaaS label
for txcd_10103000 (personal use) and txcd_10103001 (business use). Swap in
txcd_10103001 if your product is closer to a flat-rate business SaaS
subscription than a pay-per-call API.
Save the changes
Send the entire billing-profile.json back to the same URL:
The next invoice for a customer in a country you’re registered in will carry the right tax line.
Best-effort first, strict later
The enforced flag controls what Stripe does when tax calculation fails.
Calculation can fail for a handful of predictable reasons: a customer in a
country where you don’t yet have a Stripe Tax registration, a missing or invalid
postal code, or an address Stripe Tax can’t map to a known jurisdiction.
The flag decides what happens next. Either those failures become a silent gap on the invoice, or they become a hard error that blocks the subscription from being created.
enabled | enforced | Behaviour |
|---|---|---|
false | false | No tax calculation. Invoices are created without tax lines. |
true | false | Best-effort. Tax is calculated where possible. If the customer’s country has no registration, no tax. |
true | true | Strict. If Stripe Tax errors (no registration, invalid address), the invoice fails and signup is blocked. |
Strict mode sounds like the safe default. It’s the loudest way to break signup.
A French startup signs up, you don’t have an FR registration yet, the invoice
errors, they bounce. Best-effort lets the signup land, the invoice goes out
without tax, and you fix the gap by adding the registration. Once you’ve
registered everywhere your customers actually live, flip enforced to true so
a missing registration becomes a noisy error instead of a silent
under-collection.
Common mistake:
Turning on enforced: true before adding registrations for every country your
customers can sign up from. The first invoice from an unregistered country
fails, the customer can’t subscribe, and you’ve blocked your own funnel. Stay
in best-effort mode until your Stripe registrations cover every country a real
customer might sign up from, then promote to strict once that map is in place.
What the customer sees
The same defaultTaxConfig produces the right invoice for each jurisdiction:
- UK consumer: listed price plus a VAT line at 20%, calculated from their billing address.
- French business with a verified VAT number: listed price plus a “VAT reverse charge” line at zero. Stripe Tax recognises B2B intra-EU sales and applies the reverse-charge mechanism without per-customer wiring.
- US customer in a state where you have nexus: listed price plus state and local sales tax.
- US customer in a state where you don’t: listed price, no tax line.
Each invoice carries the right line, and you don’t write any of it.
When to enable tax collection
The trigger is the first paying customer outside your home jurisdiction. Once
they sign up, the clock starts on the local rule and the only thing that gets
harder by waiting is the back-tax bill. Turn enabled: true with
enforced: false the same week you publish your first paid plan, then flip to
enforced: true once your registration map catches up with your customer map.
None of this work moves a metric your team cares about. Doing it inside the gateway you’ve already wired up for billing is the difference between shipping a feature and standing up a second system to keep the first one compliant.
Tax Collection Reference
Full reference for enabling Stripe Tax on a Zuplo billing profile, the enforcement modes, tax behaviour config, and the troubleshooting paths for missing tax lines.
