← Projects

Tax integration between Shopify and CloudInvoice

A TypeScript Worker that turns every paid order into a tax document in CloudInvoice, with idempotency, on-demand customer and product creation, and stock deduction.

TypeScriptCloudflare WorkersShopify APICloudInvoice APIWebhooks
Status
In production
Year
2026
Context
Fashion retail · operation in Portugal
0
Tax documents issued during the dry run
Production
Chain proven, waiting for the first sale
Tax integration between Shopify and CloudInvoice

The problem

The store sold online and someone opened CloudInvoice, the company’s tax ERP, to enter every order by hand. Beyond the time it took, it was a constant source of error: wrong tax series, wrong warehouse, a product family that did not match the registry. The same legal entity also runs a physical operation, so the document had to come out isolated by series, warehouse and family. Without that, the bookkeeping of the two fronts blended together.

What I built

A Cloudflare Worker, in TypeScript, sitting between Shopify and CloudInvoice:

  • Listens to the store’s paid-order webhook.
  • Translates the order into the tax document format, applying the company’s rules for series, warehouse and family.
  • Creates customers and products on demand when an order brings someone or something that does not exist in the registry yet, instead of failing and requiring manual registration first.
  • Deducts stock in the same operation.
  • Issues the document and returns the generated number.

Technical decisions

Idempotency before anything else. A webhook is not delivered exactly once, it is delivered at least once. Without an idempotency key tied to the order, a redelivery produces a duplicate invoice, and on a tax document that is an accounting problem, not a software bug. Every order is marked before issuance, and reprocessing returns the document already created instead of issuing another one.

A Worker instead of a server. Volume is low and irregular, with spikes during campaigns and silence the rest of the time. With no server there is no idle machine costing money and no forgotten host going unpatched, which is the kind of thing that becomes a security incident six months later. It scales on its own during the spike.

Dry-run mode. Testing a tax integration is different from testing everything else: an error here does not produce a red log line, it produces a legal document someone has to void afterwards. So I built a mode that walks the entire chain against the real company, assembles the document, checks the total and deletes the draft at the end, without issuing anything.

Explicit failure, visible from the outside. When CloudInvoice rejects a document, the error is recorded along with the order that caused it, instead of being swallowed. On top of that, a health endpoint exposes the last invoice issued and an external monitor sends an email when it stops responding. An integration that fails silently is worse than one that does not exist: nobody notices until the month is closed.

How I verified it

The dry run went through production, against the real company. A test order produced the draft with the exact total, on the right series, creating along the way the shipping product that did not exist in the registry yet. The draft was deleted at the end. No tax document was issued.

It was by checking that document against the order, field by field, that an error the log did not show turned up: the net price was being rounded before the tax system reapplied the tax, and the total came out a few cents off. It only appeared in the comparison.

I also reprocessed the same order to confirm that idempotency holds the duplicate back, and exercised both the new-customer and new-product paths.

What is still missing: the first real sale. The chain is proven in production and the store is in its launch phase.