Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Revenue
Platforms and marketplaces
Money management
Developer resources
Overview
About Stripe payments
Upgrade your integration
Payments analytics
Online payments
OverviewFind your use caseManaged Payments
Use Payment Links
Build a checkout page
Build an advanced integration
    Overview
    Quickstart
    Design an advanced integration
    Customize look and feel
    Manage payment methods
    Collect additional information
    Collect taxes on your payments
    Save the payment method used for a payment
    Save a payment method without making a payment
    Send receipts and paid invoices
Build an in-app integration
Payment methods
Add payment methods
Manage payment methods
Faster checkout with Link
Payment interfaces
Payment Links
Checkout
Web Elements
In-app Payments
Payment scenarios
Handle multiple currencies
Custom payment flows
Flexible acquiring
Orchestration
In-person payments
Terminal
Beyond payments
Incorporate your company
Crypto
Financial Connections
Climate
Understand fraud
Radar fraud protection
Manage disputes
Verify identities
HomePaymentsBuild an advanced integration

Tax API for Sales Tax, GST, and VAT

Use Stripe Tax APIs to implement tax calculations in your custom integration.

Stripe Tax APIs enable you to calculate tax in custom payment flows. After your customer completes their payment, record the transaction so it appears in Stripe Tax reporting. The examples in this guide use Stripe payments APIs, but you can use the Tax API with any payment processor, or multiple payment processors.

Integrate Stripe Tax with Payment Links, Checkout, Billing, and Invoicing with no or low code setups.

If your custom payment flow uses PaymentIntents, see Calculate tax in your custom payment flows. This integration is in public preview and offers receipts and Dashboard support.

Get started with a video demo

This short video walks through a Stripe Tax API integration that uses PaymentIntents and the Payment Element.

Loading video content...

Add registrations

Stripe Tax only calculates tax in jurisdictions where you’re registered to collect tax and requires you to add your registrations in the Dashboard.

OptionalCollect customer address
Client-side

In most cases, the tax you collect depends on your customer’s location. Collecting your customer’s full address gives the most accurate tax calculation result. Before collecting an address, you can show your customer an estimate based on their IP address.

Note

The examples below use a simple custom address form, but you can also use the Address Element. The Address Element helps you collect addresses from customers with autocomplete and localization features.

The form below collects a full postal address:

checkout.html
<form> <label for="address_line1">Address Line 1</label> <input type="text" id="address_line1" /> <label for="address_city">City</label> <input type="text" id="address_city" /> <label for="address_state">State</label> <select id="address_state"> <option value="WA">Washington</option> <!-- add more states here --> </select> <label for="address_postal_code">Postal code</label> <input type="text" id="address_postal_code" /> <label for="address_country">Country</label> <select id="address_country"> <option value="US">United States</option> <option value="DE">Germany</option> <option value="IE">Ireland</option> <!-- add more countries here --> </select> </form>

You might pass the address to your server endpoint like this:

checkout.js
const address = { line1: document.getElementById('address_line1').value, city: document.getElementById('address_city').value, state: document.getElementById('address_state').value, postal_code: document.getElementById('address_postal_code').value, country: document.getElementById('address_country').value, }; var response = fetch('/preview-cart', { method: 'POST', body: JSON.stringify({address: address}), headers: {'Content-Type': 'application/json'}, }).then(function(response) { return response.json(); }).then(function(responseJson) { // Handle errors, or display calculated tax to your customer. });

The address information required to calculate tax varies by customer country:

  • United States: We require your customer’s postal code at a minimum. We recommend providing a full address for the most accurate tax calculation result.
  • Canada: We require your customer’s postal code or province.
  • Everywhere else: We only require your customer’s country code.

Calculate tax
Server-side

You choose when and how often to calculate tax. For example, you can:

  • Show a tax estimate based on your customer’s IP address when they enter your checkout flow
  • Recalculate tax as your customer types their billing or shipping address
  • Calculate the final tax amount to collect when your customer finishes typing their address

Stripe charges a fee per tax calculation API call. You can throttle tax calculation API calls to manage your costs.

The examples below show how to calculate tax in a variety of scenarios. Stripe Tax only calculates tax in jurisdictions where you’re registered to collect tax and requires you to add your registrations in the Dashboard.

This example calculates tax for a US shipping address. The line item has a price of 10 USD and uses your account’s preset tax code.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "customer_details[address][line1]"="920 5th Ave" \ -d "customer_details[address][city]"=Seattle \ -d "customer_details[address][state]"=WA \ -d "customer_details[address][postal_code]"=98104 \ -d "customer_details[address][country]"=US \ -d "customer_details[address_source]"=shipping

The calculation response contains amounts you can display to your customer, and use to take payment:

AttributeDescription
amount_totalThe grand total after calculating tax. Use this to set the PaymentIntent amount to charge your customer.
tax_amount_exclusiveThe amount of tax added on top of your line item amounts and shipping cost. This tax amount increases the amount_total. Use this to show your customer the amount of tax added to the transaction subtotal.
tax_amount_inclusiveThe amount of tax that’s included in your line item amounts and shipping cost (if using tax-inclusive pricing). This tax amount doesn’t increase the amount_total. Use this to show your customer the tax included in the total they’re paying.
tax_breakdownA list of tax amounts broken out by country or state tax rate. Use this to show your customer the specific taxes you’re collecting.

Handling customer location errors

The calculation returns the customer_tax_location_invalid error code if your customer’s address is invalid or isn’t precise enough to calculate tax:

{ "error": { "doc_url": "https://docs.stripe.com/error-codes#customer-tax-location-invalid", "code": "customer_tax_location_invalid", "message": "We could not determine the customer's tax location based on the provided customer address.", "param": "customer_details[address]", "type": "invalid_request_error" } }

When you receive this error, prompt your customer to check the address they’ve entered and fix any typos.

Use calculation with another processor

If you process transactions outside of Stripe, you can skip the following steps and apply the calculation to your externally-processed transactions.

Create tax transaction
Server-side

Creating a tax transaction records the tax you’ve collected from your customer, so that later you can download exports and generate reports to help with filing your taxes. You can create a transaction from a calculation until the expires_at timestamp, 90 days after it’s created. Attempting to use it after this time returns an error.

Note

The transaction is considered effective on the date when create_from_calculation is called, and tax amounts won’t be recalculated.

When creating a tax transaction, you must provide a unique reference for the tax transaction and each line item. The references appear in tax exports to help you reconcile the tax you collected with the orders in your system.

For example, a tax transaction with reference pi_123456789, line item references L1 and L2, and a shipping cost, looks like this in the itemized tax exports:

IDline_item_idtypecurrencytransaction_date…
pi_123456789L1externalusd2023-02-23 17:01:16…
pi_123456789L2externalusd2023-02-23 17:01:16…
pi_123456789shippingexternalusd2023-02-23 17:01:16…

When your customer pays, use the calculation ID to record the tax collected. Two ways to do this are:

  • If your server has an endpoint where your customer submits their order, you can create the tax transaction after the order is successfully submitted.
  • Listen for the payment_intent.succeeded webhook event. Retrieve the calculation ID from the PaymentIntent metadata.

The example below creates a transaction and uses the PaymentIntent ID as the unique reference:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/transactions/create_from_calculation \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d calculation={{TAX_CALCULATION}} \ -d reference=
{{PAYMENT_INTENT_ID}}
\ -d "expand[]"=line_items

Store the tax transaction ID so that later you can record refunds. You can store the transaction ID in your database or in the PaymentIntent’s metadata:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/payment_intents/
{{PAYMENT_INTENT_ID}}
\ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d "metadata[tax_transaction]"={{TAX_TRANSACTION}}

Record refunds
Server-side

After creating a tax transaction to record a sale to your customer, you might need to record refunds. These are also represented as tax transactions, with type=reversal. Reversal transactions offset an earlier transaction by having amounts with opposite signs. For example, a transaction that recorded a sale for 50 USD might later have a full reversal of -50 USD.

When you issue a refund (using Stripe or outside of Stripe) you need to create a reversal tax transaction with a unique reference. Common strategies include:

  • Append a suffix to the original reference. For example, if the original transaction has reference pi_123456789, then create the reversal transaction with reference pi_123456789-refund.
  • Use the ID of the Stripe refund or a refund ID from your system. For example, re_3MoslRBUZ691iUZ41bsYVkOg or myRefund_456.

Choose the approach that works best for how you reconcile your customer orders with your tax exports.

Fully refund a sale

When you fully refund a sale in your system, create a reversal transaction with mode=full.

In the example below, tax_1MEFAAI6rIcR421eB1YOzACZ is the tax transaction recording the sale to your customer:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/transactions/create_reversal \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d mode=full \ -d original_transaction=tax_1MEFAAI6rIcR421eB1YOzACZ \ -d reference=pi_123456789-cancel \ -d "expand[]"=line_items

This returns the full reversal transaction that’s created:

{ "id": "tax_1MEFtXI6rIcR421e0KTGXvCK", "object": "tax.transaction", "created": 1670866467, "currency": "eur", "customer": null, "customer_details": { "address": { "city": null, "country": "IE",

Fully reversing a transaction doesn’t affect previous partial reversals. When you record a full reversal, make sure you fully reverse any previous partial reversals for the same transaction to avoid duplicate refunds.

Partially refund a sale

After issuing a refund to your customer, create a reversal tax transaction with mode=partial. This allows you to record a partial refund by providing the line item amounts refunded. You can create up to 30 partial reversals for each sale. Reversing more than the amount of tax you collected returns an error.

The example below records a refund of only the first line item in the original transaction:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/transactions/create_reversal \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d mode=partial \ -d original_transaction=tax_1MEFAAI6rIcR421eB1YOzACZ \ -d reference=pi_123456789-refund_1 \ -d "line_items[0][original_line_item]"=tax_li_MyBXPByrSUwm6r \ -d "line_items[0][reference]"=L1 \ -d "line_items[0][amount]"=-4999 \ -d "line_items[0][amount_tax]"=-1150 \ -d "metadata[refund]"=
{{REFUND_ID}}
\ --data-urlencode "metadata[refund_reason]"="Refunded line 1 of pi_123456789 (customer was unhappy)" \ -d "expand[0]"=line_items

This returns the partial reversal transaction that’s created:

{ "id": "tax_1MEFACI6rIcR421eHrjXCSmD", "object": "tax.transaction", "created": 1670863656, "currency": "eur", ... "line_items": { "object": "list", "data": [ {

For each line item reversed you need to provide the amount and amount_tax reversed. The amount is tax-inclusive if the original calculation line item was tax-inclusive.

How amount and amount_tax are determined depends on your situation:

  • If your transactions always have a single line item, use full reversals instead.
  • If you always refund entire line items, use the original transaction line item amount and amount_tax, but with negative signs.
  • If you refund parts of line items, you need to calculate the amounts refunded. For example, for a sale transaction with amount=5000 and amount_tax=500, after refunding half the line item you’d create a partial reversal with line item amount=-2500 and amount_tax=-250.

Partially refund a sale by a flat amount

Alternatively, you can create a reversal with mode=partial by specifying a flat after-tax amount to refund. The amount distributes across each line item and shipping cost proportionally, depending on the remaining amount left to refund on each.

In the example below, the transaction has two line items: one 10 USD item and one 20 USD item, both taxed at 10%. The total amount of the transaction is 33.00 USD. A refund for a flat 16.50 USD is recorded:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/transactions/create_reversal \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d mode=partial \ -d original_transaction=tax_1NVcKqBUZ691iUZ4xMZtcGYt \ -d reference=pi_234567890-refund_1 \ -d flat_amount=-1650 \ -d "metadata[refund]"=
{{REFUND_ID}}
\ --data-urlencode "metadata[refund_reason]"="Refunded $16.50 of pi_234567890 (customer was unhappy)" \ -d "expand[]"=line_items

This returns the partial reversal transaction that’s created:

{ "id": "tax_1NVcQYBUZ691iUZ4SBPukGa6", "object": "tax.transaction", "created": 1689780994, "currency": "usd", ... "line_items": { "object": "list", "data": [ {

For each line item and shipping cost in the original transaction, the refunded amounts and tax are calculated as follows:

  1. First, we calculate the total remaining funds in the transaction available to refund. Because this transaction hasn’t had any other reversals recorded, the total amount is 33.00 USD.
  2. Next, we calculate the total amount to refund for each line item. We base this calculation on the proportion of the item’s total available amount to refund versus the total remaining amount of the transaction. For example, the 10 USD item, which has 11.00 USD total remaining to refund, represents 33.33% of the transaction’s remaining total, so the total amount to refund is -16.50 USD * 33.33% = -5.50 USD.
  3. Finally, the total amount to refund is divided between amount and amount_tax. We also do this proportionally, depending on how much tax is available to refund in the line item compared to the total funds left to refund. Using the 10 USD item example, tax (1.00 USD) represents 9.09% of the total remaining to refund (11.00 USD), so the amount_tax is -5.50 USD * 9.09% = -0.50 USD.

The flat amount distributes according to what’s left to refund in the transaction, not what was originally recorded. Consider this example: instead of recording a refund for a flat 16.50 USD, you first record a partial reversal for the total amount of the 10 USD item:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/transactions/create_reversal \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d mode=partial \ -d original_transaction=tax_1NVcKqBUZ691iUZ4xMZtcGYt \ -d reference=pi_234567890-refund_1 \ -d "line_items[0][original_line_item]"=tax_li_OICmRXkFuWr8Df \ -d "line_items[0][reference]"=partial_refund_l1 \ -d "line_items[0][amount]"=-1000 \ -d "line_items[0][amount_tax]"=-100 \ -d "metadata[refund]"=
{{REFUND_ID}}
\ --data-urlencode "metadata[refund_reason]"="Refunded line 1 of pi_234567890 (customer was unhappy)" \ -d "expand[0]"=line_items

After this, you record a 16.50 USD flat amount reversal:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/transactions/create_reversal \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d mode=partial \ -d original_transaction=tax_1NVcKqBUZ691iUZ4xMZtcGYt \ -d reference=pi_234567890-refund_2 \ -d flat_amount=-1650 \ -d "metadata[refund]"=
{{REFUND_ID}}
\ --data-urlencode "metadata[refund_reason]"="Refunded $16.50 of pi_234567890 (customer was still unhappy)" \ -d "expand[]"=line_items

This returns the partial reversal transaction:

{ "id": "tax_1NVxFIBUZ691iUZ4saOIloxB", "object": "tax.transaction", "created": 1689861020, "currency": "usd", ... "line_items": { "object": "list", "data": [ {

Because the total amount remaining in the transaction is now 22.00 USD and the 10 USD item is completely refunded, the 16.50 USD distributes entirely to the 20 USD item. The 16.50 USD then distributes, using the logic from step 3, into amount = -15.00 USD and amount_tax = -1.50 USD. Meanwhile, the 10 USD item in the transaction records a refund of 0 USD.

Undo a partial refund

Tax transactions are immutable but you can cancel out a partial refund by creating a full reversal of it.

You might need to do this when:

  • The payment refund fails and you haven’t provided the good or service to your customer
  • The wrong order is refunded or the wrong amounts are refunded
  • The original sale is fully refunded and the partial refunds are no longer valid

In the example below, tax_1MEFACI6rIcR421eHrjXCSmD is the transaction representing the partial refund:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/transactions/create_reversal \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d mode=full \ -d original_transaction=tax_1MEFACI6rIcR421eHrjXCSmD \ -d reference=pi_123456789-refund_1-cancel \ -d "metadata[refund_reason]"="User called to cancel because they picked the wrong item" \ -d "expand[]"=line_items

This returns the full reversal transaction that’s created:

{ "id": "tax_1MEFADI6rIcR421e94fNTOCK", "object": "tax.transaction", "created": 1670863657, "currency": "eur", ... "line_items": { "object": "list", "data": [ {

Testing

The response structure in sandboxes is identical to live mode so that you can confirm your integration is working correctly before going live.

Warning

We don’t guarantee calculations in testing environments to return up-to-date taxation results.

You’re limited to 1,000 tax calculations per day. If you need a higher limit, contact Stripe support.

For guidance on automated testing and strategies to avoid rate limits in testing environments, see Automated testing.

View tax transactions in the Dashboard

You can view all tax transactions for your account on the Tax Transactions page in the Dashboard. Click an individual transaction to see a detailed breakdown of calculated tax by jurisdiction, and by the individual products included in the transaction.

Note

This page only includes transactions and not calculations. If you expect a calculation to be visible but can’t find it on this page, check that you’ve successfully created a tax transaction from the calculation.

OptionalIntegration examples

You can calculate tax for your customer before collecting payment method details and creating a PaymentIntent. For example, you could display a shopping cart total when they provide their postal code.

In the example below, your server defines a /preview-cart endpoint, where the customer’s address is sent from your client-side form. The server combines the cart’s line items and the customer’s address to calculate tax.

server.js
Node.js
Java
Ruby
PHP
Python
Go
.NET
No results
// Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')(
'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
); const express = require('express'); const app = express(); // Parse the request body as JSON. app.use(express.json()); app.post('/preview-cart', async (req, res) => { const cart = ...; // Load the cart/order from your system // Convert each cart item to a Stripe line item object const lineItems = cart.items.map( cartItem => ({ reference: cartItem.id, amount: cartItem.unitPrice * cartItem.quantity, quantity: cartItem.quantity }) ); // Get the customer's address from the request body const address = req.body.address; // Create a tax calculation using the Stripe API const calculation = await stripe.tax.calculations.create({ currency: cart.currency, line_items: lineItems, customer_details: { address: { line1: address.line1, city: address.city, state: address.state, postal_code: address.postal_code, country: address.country, }, address_source: "billing" }, expand: ['line_items.data.tax_breakdown'] }); // Return the tax amount as a JSON response res.json({ tax_amount: calculation.tax_amount_exclusive }); }); app.listen(4242, () => { console.log('Running on port 4242'); });

When you’re ready to take payment, create a PaymentIntent from the tax calculation result. Store the tax calculation ID in the PaymentIntent’s metadata or in your own database so you can create a tax transaction when your customer completes payment.

The example below shows a server endpoint that calculates tax, creates (or updates) a PaymentIntent, and returns the result to the client. You can then display the tax to your customer. Use the client_secret to take the payment.

server.js
Node.js
Java
Ruby
PHP
Python
Go
.NET
No results
// Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')(
'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
); const express = require('express'); const app = express(); // Parse the request body as JSON. app.use(express.json()); app.post('/calculate-cart', async (req, res) => { const cart = ...; // Load the cart/order from your system // Create a tax calculation using the Stripe API const calculation = await stripe.tax.calculations.create(...); let paymentIntent; // Update the PaymentIntent if one already exists for this cart. if (cart.paymentIntent) { paymentIntent = await stripe.paymentIntents.update(cart.paymentIntent, { amount: calculation.amount_total, metadata: {tax_calculation: calculation.id}, }); } else { paymentIntent = await stripe.paymentIntents.create({ currency: cart.currency, amount: calculation.amount_total, metadata: {tax_calculation: calculation.id}, // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. automatic_payment_methods: {enabled: true}, }); } // Store PaymentIntent ID in cart or customer session. cart.paymentIntent = paymentIntent.id; // Return calculated amounts and PaymentIntent secret to the client. res.json({ total: calculation.amount_total, tax_amount: calculation.tax_amount_exclusive, client_secret: paymentIntent.client_secret }); }); app.listen(4242, () => { console.log('Running on port 4242'); });

If your integration uses the Payment Element, fetch updates from the server after updating the PaymentIntent.

OptionalCalculate tax on shipping costs
Server-side

To calculate tax on shipping costs, use the shipping_cost parameter:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "customer_details[address][line1]"="920 5th Ave" \ -d "customer_details[address][city]"=Seattle \ -d "customer_details[address][state]"=WA \ -d "customer_details[address][postal_code]"=98104 \ -d "customer_details[address][country]"=US \ -d "customer_details[address_source]"=shipping \ -d "shipping_cost[amount]"=500 \ -d "shipping_cost[tax_code]"=txcd_92010001

Pass the ID of an existing ShippingRate to use its amount, tax_code, and tax_behavior:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "customer_details[address][line1]"="920 5th Ave" \ -d "customer_details[address][city]"=Seattle \ -d "customer_details[address][state]"=WA \ -d "customer_details[address][postal_code]"=98104 \ -d "customer_details[address][country]"=US \ -d "customer_details[address_source]"=shipping \ -d "shipping_cost[shipping_rate]"=shr_1Mlh8YI6rIcR421eUr9SJzAD

OptionalEstimate taxes with an IP address
Server-side

If you provide your customer’s IP address, we geolocate it and use that location as your customer’s location. Use this to show your customer a tax estimate before they provide their postal address.

Caution

Because the location of an IP address could be some distance from the actual location of your customer, we recommend against using an IP address to determine the final amount of tax to collect.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "customer_details[ip_address]"="127.0.0.1"

OptionalCollect customer tax IDs
Server-side

In some cases, such as the cross-border supply of services, your business customer might have to account for tax on a reverse charge basis. You don’t collect the tax, and instead must issue an invoice with the text “Tax to be paid on reverse charge basis”. This informs your customer that they’re responsible for any tax on their purchase.

Provide your customer’s tax IDs to automatically determine when reverse charge applies:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "customer_details[address][country]"=IE \ -d "customer_details[address_source]"=billing \ -d "customer_details[tax_ids][0][type]"=eu_vat \ -d "customer_details[tax_ids][0][value]"=DE123456789

If you provide a tax ID with an invalid format, the calculation returns error code tax_id_invalid:

{ "error": { "code": "tax_id_invalid", "doc_url": "https://docs.stripe.com/error-codes#tax-id-invalid", "message": "Invalid value for eu_vat.", "param": "customer_details[tax_ids][0][value]", "type": "invalid_request_error" } }

The Tax API doesn’t automatically validate tax IDs against government databases. To validate a tax ID before calculating tax you need to use Customer tax ID validation.

OptionalTax-inclusive pricing
Server-side

By default, tax is calculated on top of the line item and shipping cost amounts you provide. Set the line item or shipping cost tax_behavior to inclusive to calculate the tax included in your prices.

In the example below the customer always pays 100 EUR:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=eur \ -d "line_items[0][amount]"=10000 \ -d "line_items[0][reference]"=L1 \ -d "line_items[0][tax_behavior]"=inclusive \ -d "line_items[0][tax_code]"=txcd_10103000 \ -d "customer_details[address][country]"=IE \ -d "customer_details[address_source]"=billing

The response returns the tax included:

{ ... "amount_total": 10000, ... "tax_amount_exclusive": 0, "tax_amount_inclusive": 1870, "tax_breakdown": [ { "amount": 1870, "inclusive": true, "tax_rate_details": { "country": "IE", "percentage_decimal": "23.0", "state": null, "tax_type": "vat" }, "taxability_reason": "standard_rated", "taxable_amount": 8130 } ], ... }

OptionalUse an existing Product object
Server-side

You can provide a Product object for each line item. If the product has a tax_code, Stripe uses it as the line item’s tax_code, if it’s not already populated. Other product values, including the tax_behavior and price, aren’t used during tax calculation.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "line_items[0][product]"=
{{PRODUCT_ID}}
\ -d "customer_details[address][country]"=IE \ -d "customer_details[address_source]"=billing

OptionalUse an existing Customer object
Server-side

If you provide a Customer object, we automatically copy and use the Customer’s address and tax IDs for the calculation:

  • If the Customer shipping is present, it’s copied to customer_details.address.
  • Otherwise, if the Customer address is present, it’s copied to customer_details.address.
  • Otherwise, if the Customer tax.ip_address is present, it’s copied to customer_details.ip_address.
  • Otherwise, if the Customer tax.tax_exempt is present, it’s copied to customer_details.taxability_override.

The Customer’s tax IDs are copied to customer_details.tax_ids.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d customer=
{{CUSTOMER_ID}}

OptionalOverride customer taxability
Server-side

In some cases, such as when your business customer is tax-exempt, you don’t collect tax. You can provide the tax exemption to Stripe Tax using the taxability_override parameter.

To provide the customer taxability override to your calculations:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "customer_details[address][line1]"="920 5th Ave" \ -d "customer_details[address][city]"=Seattle \ -d "customer_details[address][state]"=WA \ -d "customer_details[address][postal_code]"=98104 \ -d "customer_details[address][country]"=US \ -d "customer_details[address_source]"=billing \ -d "customer_details[taxability_override]"=customer_exempt

Reverse charge

Some regions, such as the European Union, implement a “reverse charge” scheme where the customer is responsible for accounting for tax if they’re purchasing as a business. For Stripe Tax to apply the correct tax treatment, we recommend you collect Tax IDs from your customers. Sometimes you might not have your customer’s tax IDs or you’ve separately determined that the reverse charge scheme applies. In such scenarios you can use taxability_override to force Stripe Tax to apply the reverse charge scheme.

To provide the customer taxability override to your calculations:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=eur \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "customer_details[address][country]"=IE \ -d "customer_details[address_source]"=billing \ -d "customer_details[taxability_override]"=reverse_charge

OptionalSpecify a ship-from location
Server-side

If you ship goods from a location other than your main place of business, you can provide that address for tax calculations.

To provide a ship-from location, use the ship_from_details parameter. In this example, the user is based in Florida, their customer is based in Springfield, IL, and the user is shipping the goods from Naperville, IL:

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "line_items[0][tax_behavior]"=exclusive \ -d "line_items[0][tax_code]"=txcd_99999999 \ -d "customer_details[address][city]"=Springfield \ -d "customer_details[address][state]"=IL \ -d "customer_details[address][postal_code]"=62704 \ -d "customer_details[address][country]"=US \ -d "customer_details[address_source]"=billing \ -d "ship_from_details[address][city]"=Naperville \ -d "ship_from_details[address][state]"=IL \ -d "ship_from_details[address][postal_code]"=60540 \ -d "ship_from_details[address][country]"=US

The response returns tax calculated based on the shipping origin of the order (Naperville, IL) instead of the destination (Springfield, IL) or the seller’s business origin:

{ ... "amount_total": 1078, ... "tax_amount_exclusive": 78, ... "tax_breakdown": [ { "amount": 78, "inclusive": true, "tax_rate_details": { "country": "US", "percentage_decimal": "7.75", "state": "IL", "tax_type": "sales_tax" }, "taxability_reason": "standard_rated", "taxable_amount": 1000 } ], ... }

To learn more about how we calculate taxes in these scenarios, see the Stripe Tax documentation.

OptionalCalculate the retail delivery fee
Server-side

Stripe Tax supports calculating the retail delivery fee in Minnesota and Colorado.

After you add a tax registration of the type state_retail_delivery_fee in the supported states, the retail delivery fee gets calculated on tax calculations.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/registrations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d country=US \ -d "country_options[us][state]"=CO \ -d "country_options[us][type]"=state_retail_delivery_fee \ -d active_from=now

To calculate, call the tax calculations API using a physical item product tax code, such as txcd_30011000, which represents “Clothing & Footwear.” You can find the physical item tax codes in our Product tax codes documentation.

Not all physical items trigger the retail delivery fee to be calculated. Refer to the states documentation on when the tax applies:

  • Retail Delivery Fee—Colorado
  • Retail Delivery Fee—Minnesota
Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "line_items[0][tax_behavior]"=exclusive \ -d "line_items[0][tax_code]"=txcd_30011000 \ -d "shipping_cost[amount]"=400 \ -d "customer_details[address][line1]"="1437 Bannock St Room 451" \ -d "customer_details[address][city]"=Springfield \ -d "customer_details[address][state]"=CO \ -d "customer_details[address][postal_code]"=80202 \ -d "customer_details[address][country]"=US \ -d "customer_details[address_source]"=shipping

The response returns tax calculated with the retail delivery fee (Colorado). It’s an additional entry in the tax_breakdown object, withtax_breakdown.tax_rate_details.rate_type set to flat_amount:

{ ... "amount_total": 2165, ... "tax_amount_exclusive": 165, ... "tax_breakdown": [ { "amount": 88, "inclusive": false, "tax_rate_details": { "percentage_decimal": "8.81", "rate_type": "percentage", "tax_type": "sales_tax", ... }, "taxability_reason": "standard_rated", "taxable_amount": 1000 }, ... { "amount": 29, "inclusive": false, "tax_rate_details": { "flat_amount": { "amount": 29, "currency": "usd" }, "percentage_decimal": "0.0", "rate_type": "flat_amount", "tax_type": "retail_delivery_fee", ... }, "taxability_reason": "standard_rated", "taxable_amount": 1000 } ], ... }

OptionalDetailed line item tax breakdowns
Server-side

The top-level tax_breakdown is always returned and provides a simple breakdown suitable for displaying a list of taxes at checkout or on a receipt.

You can use the taxability_reason to understand why tax isn’t being applied while building your integration. For example, not_collecting means that you’re not collecting tax in the country or state where tax would be due. Adding tax registrations to your account settings tells Stripe where you’re collecting tax. If you have already added registration for Washington, the taxability reason displayed in your result is standard_rated, which indicates that the product is taxed at the standard rate.

Expand the line item tax_breakdown attribute to get a detailed breakdown, including local taxes and attributes explaining the reason for each tax.

  • The tax_type field from tax_rate_details is a high-level tax type indication that might not always match the type returned in reports and transaction exports. For example, it doesn’t distinguish between US sales tax and US use tax.
  • Use the display_name field from tax_rate_details in your Checkout flow to show all of the taxes. The taxes are localized based on customer location and product tax information. For example, if VAT is applied for Germany because the customer is in Germany and the product is taxed at the destination, such as txcd_10103001: Software as a service (SaaS) for business use, we show Umsatzsteuer (USt), which is the German representation for VAT. If VAT is applied for France because the head office address is set to France and the product is taxed at the origin, such as txcd_20030000: General - Services, we show Taxe sur la valeur ajoutée (TVA), which is the French representation of VAT.
Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl https://api.stripe.com/v1/tax/calculations \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "customer_details[address][line1]"="920 5th Ave" \ -d "customer_details[address][city]"=Seattle \ -d "customer_details[address][state]"=WA \ -d "customer_details[address][postal_code]"=98104 \ -d "customer_details[address][country]"=US \ -d "customer_details[address_source]"=shipping \ -d "expand[0]"="line_items.data.tax_breakdown"
{ ... "tax_breakdown": [ { "amount": 103, "inclusive": false, "tax_rate_details": { "country": "US", "percentage_decimal": "10.25", "state": "WA", "tax_type": "sales_tax" }, "taxability_reason": "standard_rated", "taxable_amount": 1000 } ],

OptionalTroubleshoot common errors
Server-side

Resolve invalid tax code errors

If you get the error Invalid tax code, see Product tax codes for the list of available tax codes. Then follow these steps to resolve the issue:

  1. Check the tax code: Ensure that you’re using a valid tax code from the list of available tax codes. Common mistakes include:

    • Using an empty string or null as the tax code
    • Misspelling the tax code
    • Using a non-existent tax code
  2. Update your code: Make sure you pass a valid tax code when creating a TaxCalculation. For example:

    Command Line
    cURL
    Stripe CLI
    Ruby
    Python
    PHP
    Java
    Node.js
    Go
    .NET
    No results
    curl https://api.stripe.com/v1/tax/calculations \ -u "
    sk_test_BQokikJOvBiI2HlWgH4olfQ2
    :"
    \ -d currency=usd \ -d "line_items[0][amount]"=1000 \ -d "line_items[0][reference]"=L1 \ -d "line_items[0][tax_code]"=txcd_10000000 \ -d "customer_details[address][line1]"="354 Oyster Point Blvd" \ -d "customer_details[address][city]"="South San Francisco" \ -d "customer_details[address][state]"=CA \ -d "customer_details[address][postal_code]"=94080 \ -d "customer_details[address][country]"=US \ -d "customer_details[address_source]"=shipping
  3. Use the default tax code: Stripe Tax uses a default tax code for calculations when a specific tax code isn’t provided. You can view and update this default in your Tax Settings.

    To update the default tax code using the API:

    Command Line
    cURL
    Stripe CLI
    Ruby
    Python
    PHP
    Java
    Node.js
    Go
    .NET
    No results
    curl https://api.stripe.com/v1/tax/settings \ -u "
    sk_test_BQokikJOvBiI2HlWgH4olfQ2
    :"
    \ -d "defaults[tax_code]"=txcd_10000000

    This default tax code applies when no specific tax code is provided for a product or in a tax calculation request.

  4. Review your product catalog: If you’re using tax codes associated with products in your Stripe product catalog, ensure the tax codes are correctly assigned to your products.

  5. Check for data inconsistencies: Ensure the tax code is correctly passed from your database or front end to your server-side code that makes the API call to Stripe.

For more accurate tax calculations, use the most specific tax code that applies to your product or service. If you’re unsure which tax code to use, consult the tax codes documentation.

If you continue to experience issues, review the Tax Settings API documentation.

See also

  • Use Stripe Tax with Connect
Was this page helpful?
YesNo
  • Need help? Contact Support.
  • Join our early access program.
  • Check out our changelog.
  • Questions? Contact Sales.
  • LLM? Read llms.txt.
  • Powered by Markdoc