Skip to content

Case study / B2B SaaS platform / Part 2 of 2

Integrating an e-invoicing API without a sandbox

Short answerAn accepted request can still produce an incorrect invoice. Retry identity, tax semantics and automatic submission each need their own checks.

A B2B SaaS platform needed to invoice paid orders automatically through the company's existing e-invoicing service. The integration did not have a usable vendor sandbox. A request could create a real document, which made retry behavior and rollout controls part of the implementation rather than tasks to consider afterwards.

My first goal was not simply to receive a successful API response. The workflow also had to resume after failure without creating another customer, order or invoice for work it had already completed.

Using the billable event as the identity

I initially treated an order becoming paid as the invoice trigger. Reading the other payment paths showed that this was incomplete. Payments taken inside a service workflow and subscription renewals did not all pass through the same order-state transition.

I attached the invoice record to the billable event instead. Each event has a unique, non-null key. A queued job progresses through the recorded stages and reuses completed results rather than starting each external operation again. A retry therefore resumes a specific piece of work instead of repeating the whole workflow.

What the mocked responses had not established

The test responses I wrote from the documentation did not describe every detail of the live service. A field I intended to use for customer identification was not populated as expected for API-created customers. Order items also arrived inside a different response structure from the one in my fixtures.

Those differences exposed a boundary in the test evidence. The application could process the response I had written; the tests had not established that this response matched the live contract. Once real responses were available, I corrected the checks and the fixtures against them.

Checking tax and discount semantics separately

Matching the order total was not enough to establish a correct invoice. The service expected the price and discount on different tax bases. Sending an undiscounted gross price could produce an accepted request while still calculating the wrong taxable amount.

I treated unit price, discount and VAT as separate parts of the contract. Verification included the values on the resulting document, rather than stopping at an HTTP status. This matters because a valid request can be commercially incorrect without producing a transport error.

Enabling automatic submission in stages

I designed the release so invoice records could be created while automatic submission remained disabled. The first document could then be sent deliberately and checked in the vendor's panel before other payments started producing invoices automatically.

That separation also made it possible to distinguish application behavior from the company's existing document settings. An external service accepting a request did not establish that its downstream numbering or document processing would finish successfully.

A controlled rollout does not replace a sandbox. It keeps three questions separate: whether the application can resume work, whether the external data contract is correct, and whether the account can issue the expected document. Each needs its own evidence.

Work together

Thinking about a similar project?

Tell me about your project