Writing · back to the section

Stripe subscriptions and automated local invoicing

Stripe collects the subscription, but the invoice still has to be produced under local rules. This is about connecting the two — webhooks, currency, cancellation, and what's easy to get wrong.

Stripe works well for a small business, but there is one thing it doesn't provide: an invoice that satisfies local tax rules. What the customer receives from Stripe is a record of payment, not an invoice in the sense your tax authority means. On a subscription that would mean recurring manual work every month: somebody writing an invoice at each renewal.

The connection can be automated. Stripe tells your system about every successful charge, and your system issues the invoice through the invoicing provider and sends it to the customer — seconds after payment, with no manual step. This piece describes how that is built and where the traps are. I've built and run such a system; here I'm writing about the how.

The invoice's path from payment to customer Stripe signals a successful charge; the system verifies the signature, records the event, issues the invoice and it is sent to the customer. charge signature check invoice issued customer nobody is present at any of these steps — · the system verifies, records and issues
  1. 01 charge
  2. 02 signature check
  3. 03 invoice issued
  4. 04 customer

nobody is present at any of these steps — · the system verifies, records and issues

How does the system know a payment succeeded?

Stripe reports state changes with webhook events: payment, renewal, failed charge — it posts a notification to a URL of yours. This is the right direction: your system doesn't poll, Stripe speaks when something happened.

With subscriptions the choice of event matters. The end of a first purchase is signalled by checkout completion, but renewals are reported only by the billing events — the customer isn't on the site then, the charge happens in the background. Invoicing should therefore be tied to the successful-charge event, not to checkout completion: that one arrives on every renewal, and on the first purchase too.

The incoming event has to be verified before the system acts on it: Stripe signs every webhook, and the signature proves the event really came from them. Without that, anyone who knows the URL could send a fake “payment succeeded” event — which here would mean a fake invoice.

How does an event become an invoice?

Invoicing providers offer an interface for this: your system extracts the customer and line-item data from the Stripe event, assembles a request, and the provider issues the invoice — returning its number, and sending it to the customer by e-mail on request.

The line-item wording isn't a developer decision. Local rules prescribe what may appear on an invoice. The wording and the tax treatment should therefore be settled with an accountant before the first live invoice — the code then holds to that. I don't invent invoice fields either.

What about duplicate events?

A webhook can arrive more than once. That isn't a fault but the nature of delivery: if the response is slow or fails, the sender retries. Without protection this means two invoices for one payment, and an invoice can't simply be deleted.

The remedy is to record processed events by their identifier, and to check that record before issuing. An event already seen gets acknowledged and skipped. This is the point where the difference between a working system and an almost-working one shows up — and it only shows up in production.

Questions on this topic

Isn't Stripe's own receipt enough as an invoice?

Whether an invoice is required, for whom and with what content is a tax question for an accountant, not for this article. The technical side is unambiguous: what Stripe issues is a record of the payment, not an invoice under local rules. If an invoice is needed, a separate system has to produce it.

What if the invoicing service is unavailable?

The payment still succeeded; only the invoice doesn't get issued at that moment. So the invoicing code should record the missed invoice on failure, send an alert, and retry later — or allow it to be issued by hand. The important part is that the omission is visible, not that it never happens.

Why tie invoicing to the charge event rather than checkout?

Because checkout completion only happens on the first purchase. Renewals are charged in the background while the customer isn't on the site, and only the billing events report them. Tying invoicing to the successful-charge event covers both the first purchase and every renewal.

Do you need a separate server for this?

Not necessarily. The webhook receiver and the invoicing code will run on ordinary hosting alongside an existing website. What is needed: a public URL that Stripe can reach, and somewhere the system can reliably store state — issued invoices and processed events.

The decisions in this article exist as running code as well: stripe-szamlazz-hu — signature verification, event reservation, and a test that runs without a network.

If you collect payments with Stripe — or are about to — and invoicing still happens by hand, send me an e-mail describing how it works today. I'll write back what could be automated, and roughly how much work it is.

kristof@kristofkarner.com

← Back to Writing