Handling VAT with Stripe: Follow up

Thijs Cadier

Thijs Cadier on

Handling VAT with Stripe: Follow up

Last year we wrote about our approach to handling European VAT rules with Stripe. I'm happy to report that Stripe has now implemented a much better tax handling system. Meanwhile we also learned some more about properly filing VAT tax returns. Let's see how we can use this new knowledge to achieve a better result with less code.

Previously, we implemented adding VAT to an invoice for a subscription by adding an invoice line. Since API version 12/22/14 there's a tax_percent field on the Subscription object. When you use this field, Stripe will automatically make the calculation and add the VAT amount to every invoice that's generated for the subscription.

So the first step, if you use a similar approach as described in our earlier blog post, is removing the extra logic on the webhook for invoice.created. We don't need to modify the invoice anymore. Instead we'll add the tax_percent field to the subscription. We still need to have a way to determine if a customer needs to pay VAT though.

Our accountant informed us that even though we were applying the right VAT percentages to all invoices, we do need to separately categorize all the revenue from non-VAT paying EU customers. That's called "reverse charged" in accountancy parlance. So we now use these two methods to determine the right tax status:

  # If a customer is based in the Netherlands, or within the EU while not having
  # supplied a valid VAT number.
  def vat_complicit?
    country == HOME_COUNTRY ||
        AppsignalServer.eu_countries.include?(country_name) &&
  # If a customer is within the EU, but not in the Netherlands, and has supplied
  # a valid VAT number.
  def vat_reverse_charged?
    country != HOME_COUNTRY &&
      AppsignalServer.eu_countries.include?(country_name) &&

Disclaimer: This is only accurate if you run a service that sells to businesses primarily. Please check with your accountant: we can't tell if things might be different in your specific situation. We're not providing you with legal or bookkeeping advice, and can't be hold accountable as such.

Now we know everything we need to know, and we can actually create a subscription. All the logic we needed earlier is gone, as we just add the tax_percent field when updating the subscription for a customer in Stripe:

  :plan        => plan.stripe_slug,
  :prorate     => plan_change_is_an_upgrade?,
  :tax_percent => vat_complicit? ? VAT_PERCENTAGE : nil

Whenever an invoice has been created and paid, we add it to our SaaS invoicing system MoneyBird. Instead of having one tax category for non-VAT invoices we now have two, and select the correct one like this:

tax_rate = if account.vat_complicit?
           elsif account.vat_reverse_charged?
tax_rate_id = MONEYBIRD_CONFIG[tax_rate]

That's all there is to it now. Big thanks to Stripe for making handling subscriptions nicer for their European customers!

Share this article

Thijs Cadier

Thijs Cadier

Thijs is a co-founder of AppSignal who sometimes goes missing for months on end to work on our infrastructure. Makes sure our billions of requests are handled correctly. Holds the award for best drummer in the company.

All articles by Thijs Cadier

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps