<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: VAT Engine</title>
    <description>The latest articles on DEV Community by VAT Engine (vat-engine).</description>
    <link>https://dev.to/vat-engine</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14227%2F9387a66e-23a7-4e90-a238-4a515aebc13d.jpg</url>
      <title>DEV Community: VAT Engine</title>
      <link>https://dev.to/vat-engine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vat-engine"/>
    <language>en</language>
    <item>
      <title>EU VAT for SaaS &amp; Subscription Billing</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Fri, 18 Sep 2026 19:34:08 +0000</pubDate>
      <link>https://dev.to/vat-engine/eu-vat-for-saas-subscription-billing-3ho5</link>
      <guid>https://dev.to/vat-engine/eu-vat-for-saas-subscription-billing-3ho5</guid>
      <description>&lt;h1&gt;
  
  
  EU VAT for SaaS &amp;amp; Subscription Billing
&lt;/h1&gt;

&lt;p&gt;Selling SaaS subscriptions across the EU turns VAT into more than a simple percentage calculation.&lt;/p&gt;

&lt;p&gt;The billing flow may need to consider the customer's country, product tax treatment, whether the displayed price already includes VAT, the transaction date, refunds or corrections, and the information finance teams will need later for reporting.&lt;/p&gt;

&lt;p&gt;VAT Engine is designed to keep those pieces connected instead of forcing every SaaS product to maintain its own VAT tables and tax logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  EU VAT becomes part of the billing architecture
&lt;/h2&gt;

&lt;p&gt;A typical SaaS billing system is already responsible for subscriptions, renewals, invoices, payments, cancellations, and refunds.&lt;/p&gt;

&lt;p&gt;EU VAT adds another layer of decisions.&lt;/p&gt;

&lt;p&gt;For a transaction, your application may need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;which country the sale belongs to&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax class&lt;/a&gt; applies to the product&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which &lt;a href="https://vat-engine.app/docs/api/rates" rel="noopener noreferrer"&gt;VAT rate&lt;/a&gt; should be used&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;whether the amount is VAT-inclusive or VAT-exclusive&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which transaction date should be considered&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and what context should be retained for later review&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A billing system that stores only something like &lt;code&gt;VAT rate: 19%&lt;/code&gt; loses much of the information needed to understand that result later.&lt;/p&gt;

&lt;p&gt;VAT Engine separates VAT calculation from the rest of the billing implementation while keeping the relevant transaction context available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculate VAT from structured transaction data
&lt;/h2&gt;

&lt;p&gt;A SaaS application can send transaction information to the VAT Engine REST API and receive a structured &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;VAT calculation&lt;/a&gt; result.&lt;/p&gt;

&lt;p&gt;Typical calculation inputs include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;destination country&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product tax class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction amount&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;currency&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT-inclusive or VAT-exclusive pricing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction date&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows your own application to remain responsible for subscriptions and payments while VAT-specific rate resolution and calculation logic is handled separately.&lt;/p&gt;

&lt;p&gt;A simplified flow looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;**Customer → SaaS billing flow → VAT Engine → VAT result → Payment → Transaction record**&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The API can be used from a custom checkout, subscription backend, billing service, or internal application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle VAT-inclusive and VAT-exclusive pricing correctly
&lt;/h2&gt;

&lt;p&gt;The same VAT rate produces different arithmetic depending on how a price is represented.&lt;/p&gt;

&lt;p&gt;If a subscription costs €100 before VAT and the applicable rate is 20%, VAT is added to the net amount.&lt;/p&gt;

&lt;p&gt;The result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net: €100
VAT: €20
Gross: €120
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if the customer-facing €100 price already includes 20% VAT, the VAT portion is not €20.&lt;/p&gt;

&lt;p&gt;It must be extracted from the gross amount.&lt;/p&gt;

&lt;p&gt;VAT Engine makes the pricing basis explicit so the calling application does not have to infer whether VAT should be added or extracted.&lt;/p&gt;

&lt;p&gt;This can be particularly useful when the same SaaS product serves different customer types, markets, or pricing models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the transaction date when working with older records
&lt;/h2&gt;

&lt;p&gt;Subscription systems do not only process transactions created today.&lt;/p&gt;

&lt;p&gt;Teams frequently need to work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;refunds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;corrections&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;imported historical transactions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;billing migrations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;reconciliation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;audit or accountant reviews&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using today's VAT rate for every historical operation can produce a result that does not match the original transaction.&lt;/p&gt;

&lt;p&gt;VAT Engine supports date-aware lookup using recorded rate windows where historical coverage is available.&lt;/p&gt;

&lt;p&gt;This means the transaction date can participate in VAT rate resolution instead of the system automatically assuming the currently active rate.&lt;/p&gt;

&lt;p&gt;Recorded lookup windows are kept distinct from a guarantee of legally verified historical applicability. Stronger source provenance and deterministic historical replay are areas being developed further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep enough context to understand the result later
&lt;/h2&gt;

&lt;p&gt;For financial systems, returning the correct number is only part of the job.&lt;/p&gt;

&lt;p&gt;Imagine someone reviews a subscription transaction several months later and asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why was this VAT treatment applied?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A percentage alone does not answer that question.&lt;/p&gt;

&lt;p&gt;A useful transaction record can retain context such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;country&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product tax class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction date&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;original amount&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;currency&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT-inclusive or VAT-exclusive pricing basis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT result&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source identity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;calculation timestamps&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;available rate context&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping this information together makes later reconciliation, refunds, reporting, and accountant review easier than reconstructing the original VAT decision from several disconnected systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support renewals and recurring billing
&lt;/h2&gt;

&lt;p&gt;Recurring subscription billing creates a new transaction each time a customer renews.&lt;/p&gt;

&lt;p&gt;Instead of permanently hard-coding VAT rates into a billing service, the application can resolve VAT using the transaction context relevant to each renewal.&lt;/p&gt;

&lt;p&gt;This provides a cleaner separation between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;subscription logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;payment processing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;VAT calculation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;transaction records&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/compliance" rel="noopener noreferrer"&gt;compliance preparation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also means VAT-related logic can evolve without requiring every billing workflow to maintain its own copy of tax data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle refunds and corrections with historical context
&lt;/h2&gt;

&lt;p&gt;Refunds are one of the places where transaction context becomes particularly important.&lt;/p&gt;

&lt;p&gt;A refund issued today may relate to a subscription transaction created months earlier.&lt;/p&gt;

&lt;p&gt;The original:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;transaction date&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;country&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product classification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;price basis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and recorded VAT context&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can all matter when reviewing how the refund should relate to the original sale.&lt;/p&gt;

&lt;p&gt;Keeping the original transaction record available is generally more useful than attempting to reconstruct everything from today's configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare transaction data for OSS workflows
&lt;/h2&gt;

&lt;p&gt;For B2C SaaS sales across EU countries, VAT calculation is only one part of the operational process.&lt;/p&gt;

&lt;p&gt;Finance or accounting teams eventually need an organized set of transactions that can be reviewed by reporting period, country, and source.&lt;/p&gt;

&lt;p&gt;VAT Engine provides compliance-oriented workflows around committed transaction records, including support for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OSS/IOSS preparation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction review&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;accountant-oriented exports&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;multi-source reporting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;reconciliation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VAT Engine does not submit VAT returns on behalf of the business and does not replace professional tax advice.&lt;/p&gt;

&lt;p&gt;The purpose is to make the underlying VAT data easier to organize, inspect, and prepare for downstream compliance work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep multiple billing sources organized
&lt;/h2&gt;

&lt;p&gt;As a SaaS business grows, revenue may start arriving through more than one system.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the main subscription checkout&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an additional storefront&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;marketplace sales&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;imported historical billing records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;internal payment flows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;connected commerce platforms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VAT Engine uses source profiles to keep transactions attributable to the system or sales channel they came from.&lt;/p&gt;

&lt;p&gt;That provides a consistent source model for reporting and reconciliation instead of requiring separate tax-data structures for every integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use SME threshold data alongside VAT workflows
&lt;/h2&gt;

&lt;p&gt;VAT obligations can also depend on the circumstances of the business itself.&lt;/p&gt;

&lt;p&gt;VAT Engine provides &lt;a href="https://vat-engine.app/docs/api/sme-thresholds" rel="noopener noreferrer"&gt;EU SME VAT threshold&lt;/a&gt; data that can be used alongside other compliance workflows, including values represented in EUR and relevant national currencies where supported.&lt;/p&gt;

&lt;p&gt;Keeping threshold information accessible through the same platform reduces the need to maintain another independent dataset inside the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where VAT Engine fits into a SaaS stack
&lt;/h2&gt;

&lt;p&gt;VAT Engine is not intended to replace your payment processor or subscription-management platform.&lt;/p&gt;

&lt;p&gt;Your existing systems can continue handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;subscriptions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;payment methods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;invoices&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;payment collection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cancellations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;customer accounts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VAT Engine provides the VAT-specific layer around those workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;EU VAT calculation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT rate resolution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product tax classes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;date-aware rate lookup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source profiles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/sme-thresholds" rel="noopener noreferrer"&gt;SME VAT&lt;/a&gt; threshold data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/compliance#oss--ioss-tax-registrations" rel="noopener noreferrer"&gt;OSS/IOSS&lt;/a&gt; preparation workflows&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The REST API can be integrated into an existing architecture without requiring VAT Engine to become the system responsible for the entire billing lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: a new B2C subscription
&lt;/h3&gt;

&lt;p&gt;Consider a SaaS customer purchasing a subscription from another EU country.&lt;/p&gt;

&lt;p&gt;The billing application collects the required transaction context and sends the relevant data to VAT Engine.&lt;/p&gt;

&lt;p&gt;VAT Engine resolves the recorded VAT rate and calculates the VAT amounts.&lt;/p&gt;

&lt;p&gt;The billing application can then use the result when presenting or processing the transaction.&lt;/p&gt;

&lt;p&gt;After the sale, the transaction context can be retained for reporting and later review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: a subscription renewal
&lt;/h3&gt;

&lt;p&gt;When the subscription renews, the billing service creates a new transaction.&lt;/p&gt;

&lt;p&gt;The current transaction context can be evaluated again rather than assuming that every renewal must reuse tax data stored when the customer originally subscribed.&lt;/p&gt;

&lt;p&gt;This keeps recurring billing logic separate from VAT rate maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: refunding an older subscription
&lt;/h3&gt;

&lt;p&gt;A refund may relate to a transaction that occurred during an earlier reporting period.&lt;/p&gt;

&lt;p&gt;The original transaction date and recorded VAT context provide a better basis for reviewing the refund than simply applying today's rate.&lt;/p&gt;

&lt;p&gt;Date-aware lookup and retained transaction records help support that workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: migrating historical billing data
&lt;/h3&gt;

&lt;p&gt;A SaaS company moving from another billing architecture may need to import older transactions.&lt;/p&gt;

&lt;p&gt;Historical records can be organized using consistent source profiles, tax classes, countries, dates, and recorded VAT context.&lt;/p&gt;

&lt;p&gt;That creates a more structured foundation for reconciliation and future reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this use case is for
&lt;/h2&gt;

&lt;p&gt;VAT Engine can be useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SaaS businesses selling subscriptions across EU countries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;developers building custom subscription billing systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;B2C digital service providers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;finance teams preparing OSS data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;platforms that need VAT calculation through an API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;teams reconciling or migrating historical billing data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keep VAT logic outside your billing code
&lt;/h2&gt;

&lt;p&gt;A SaaS billing system already has enough responsibilities.&lt;/p&gt;

&lt;p&gt;Maintaining VAT rates, tax classifications, historical rate context, and compliance-oriented transaction records does not necessarily need to become another permanent part of that codebase.&lt;/p&gt;

&lt;p&gt;VAT Engine provides a dedicated EU VAT layer that can be integrated through the API while keeping the transaction context needed for later review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;**VAT Engine is currently available in free alpha. No payment method is required.**&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>shopify</category>
      <category>software</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why a VAT API Needs More Than a Rate Lookup</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Fri, 18 Sep 2026 19:27:11 +0000</pubDate>
      <link>https://dev.to/vat-engine/why-a-vat-api-needs-more-than-a-rate-lookup-50i9</link>
      <guid>https://dev.to/vat-engine/why-a-vat-api-needs-more-than-a-rate-lookup-50i9</guid>
      <description>&lt;p&gt;When developers first add VAT to an ecommerce or SaaS product, the problem can look surprisingly small:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Find the customer's country.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Look up the VAT rate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiply the price by that percentage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Done.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That works until the first real edge cases arrive.&lt;/p&gt;

&lt;p&gt;What product was sold? Was the displayed price VAT-inclusive? Which country actually has taxing rights? Was this transaction created today or six months ago? Is it a refund of an older sale? Which rate was used when the original transaction happened?&lt;/p&gt;

&lt;p&gt;At that point, a VAT integration stops being a percentage lookup and starts becoming a data and decision-traceability problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  A country does not have one VAT rate
&lt;/h2&gt;

&lt;p&gt;A basic VAT table might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"FR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"IT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful, but incomplete.&lt;/p&gt;

&lt;p&gt;EU member states can apply standard, reduced, zero, and other rates depending on the type of supply. Books, accommodation, food, pharmaceuticals, and other categories may receive different treatment.&lt;/p&gt;

&lt;p&gt;So the more useful question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is Germany's VAT rate?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which VAT rate applies in Germany to this product category for this transaction?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means a useful API needs product tax context, not only a country code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transaction date matters
&lt;/h2&gt;

&lt;p&gt;Using today's rate for every calculation creates another problem.&lt;/p&gt;

&lt;p&gt;Imagine processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a refund for an order placed last year,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an imported historical transaction,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an audit of an older sale,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a correction to previously recorded data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rate that is active today is not necessarily the rate that was used for the original transaction.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://vat-engine.app/docs" rel="noopener noreferrer"&gt;VAT API&lt;/a&gt; therefore benefits from accepting an explicit transaction or supply date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response can then identify the recorded rate window used for that lookup.&lt;/p&gt;

&lt;p&gt;One important distinction: a recorded historical window and a legally verified effective date are not automatically the same thing.&lt;/p&gt;

&lt;p&gt;If the underlying source data does not prove legal applicability for a historical period, the API should make that limitation explicit rather than pretending to know more than it does.&lt;/p&gt;

&lt;p&gt;Failing clearly is much safer than silently returning a convenient number.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAT-inclusive and VAT-exclusive prices are different calculations
&lt;/h2&gt;

&lt;p&gt;There is also a basic arithmetic distinction that often gets buried inside tax code.&lt;/p&gt;

&lt;p&gt;If €100 is VAT-exclusive at 20%, the total is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €100
VAT:    €20
Gross: €120
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if €100 already includes 20% VAT, the VAT portion is not €20.&lt;/p&gt;

&lt;p&gt;It is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = 100 - (100 / 1.20)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is approximately €16.67.&lt;/p&gt;

&lt;p&gt;An API should therefore require the caller to make the price basis explicit instead of guessing.&lt;/p&gt;

&lt;p&gt;For financial calculations, I also prefer integer minor units rather than floating-point money:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;10000&lt;/code&gt; represents €100.00.&lt;/p&gt;

&lt;p&gt;That avoids a whole class of floating-point rounding problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Returning the correct number is only half the job
&lt;/h2&gt;

&lt;p&gt;Suppose an API returns this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1597&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six months later, someone asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why did this transaction use 19% VAT?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The number alone cannot answer that.&lt;/p&gt;

&lt;p&gt;A more useful calculation record may also retain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;country&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product tax class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction date&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT-inclusive/exclusive basis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;input amount&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rate used&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source/store identity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;calculation timestamp&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;relevant rate-data version or provenance where available&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns the VAT result from a disposable API response into something that can actually be reviewed later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source identity matters in multi-store commerce
&lt;/h2&gt;

&lt;p&gt;The problem becomes more visible when a business has several sales channels.&lt;/p&gt;

&lt;p&gt;An order might come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/integrations/shopify" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a custom checkout&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;another marketplace&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an imported CSV&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an internal billing system&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For reporting purposes, simply storing "transaction 12345" may not be enough.&lt;/p&gt;

&lt;p&gt;The system also needs to know which source owns that transaction.&lt;/p&gt;

&lt;p&gt;That is why I think source profiles are useful as a first-class concept rather than just another string attached to an order.&lt;/p&gt;

&lt;p&gt;They make it possible to group and reconcile records without losing where those records originally came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance workflows need committed data
&lt;/h2&gt;

&lt;p&gt;There is another important boundary between a calculator and a compliance system.&lt;/p&gt;

&lt;p&gt;Someone calling a VAT calculator ten times should not automatically create ten filing records.&lt;/p&gt;

&lt;p&gt;Reporting workflows need a defined population of committed business transactions, not every exploratory API request ever made.&lt;/p&gt;

&lt;p&gt;That separation becomes important for OSS/IOSS preparation, accountant review, exports, and reconciliation.&lt;/p&gt;

&lt;p&gt;Calculation and compliance can share the same data model, but they should not be treated as the same action.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am building around these ideas
&lt;/h2&gt;

&lt;p&gt;These problems are what pushed VAT Engine beyond a simple VAT-rate endpoint.&lt;/p&gt;

&lt;p&gt;The current direction combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;EU VAT &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;calculation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TEDB-backed &lt;a href="https://vat-engine.app/docs/api/rates" rel="noopener noreferrer"&gt;rate data&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax classes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;date-aware rate lookup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source profiles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SME VAT threshold data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/compliance" rel="noopener noreferrer"&gt;OSS/IOSS&lt;/a&gt; preparation workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ecommerce integrations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is still more work to do, particularly around stronger rate provenance, immutable source evidence, and deterministic replay.&lt;/p&gt;

&lt;p&gt;The long-term goal is not just to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What VAT rate should I use?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is to make it possible to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What information produced this VAT result, and can I understand that decision later?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For financial infrastructure, I think that second question is ultimately the more interesting one.&lt;/p&gt;

</description>
      <category>api</category>
      <category>saas</category>
      <category>vat</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>What Building a Shopify Integration Taught Me About Trusting External APIs</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Sat, 01 Aug 2026 17:31:28 +0000</pubDate>
      <link>https://dev.to/vat-engine/what-building-a-shopify-integration-taught-me-about-trusting-external-apis-3hbm</link>
      <guid>https://dev.to/vat-engine/what-building-a-shopify-integration-taught-me-about-trusting-external-apis-3hbm</guid>
      <description>&lt;p&gt;Building a Shopify integration looks straightforward at first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate the store.&lt;/li&gt;
&lt;li&gt;Request orders.&lt;/li&gt;
&lt;li&gt;Transform the response.&lt;/li&gt;
&lt;li&gt;Store the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difficult part begins when the integration needs to become reliable enough for production.&lt;/p&gt;

&lt;p&gt;While building the Shopify integration for VAT Engine, I learned that consuming an external API is not only about parsing its response. It is about defining exactly what you trust, what you verify, and what must fail closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  External APIs are contracts, not just endpoints
&lt;/h2&gt;

&lt;p&gt;A GraphQL query can continue returning a successful response while the meaning of the returned data changes around it.&lt;/p&gt;

&lt;p&gt;An integration depends on more than the endpoint itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API version&lt;/li&gt;
&lt;li&gt;requested fields&lt;/li&gt;
&lt;li&gt;required access scopes&lt;/li&gt;
&lt;li&gt;pagination behaviour&lt;/li&gt;
&lt;li&gt;nullable fields&lt;/li&gt;
&lt;li&gt;partial GraphQL errors&lt;/li&gt;
&lt;li&gt;protected customer-data permissions&lt;/li&gt;
&lt;li&gt;webhook topics&lt;/li&gt;
&lt;li&gt;authentication requirements&lt;/li&gt;
&lt;li&gt;assumptions made during transformation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That entire combination is the real contract.&lt;/p&gt;

&lt;p&gt;If any part changes silently, the integration may continue running while producing incomplete or misleading results.&lt;/p&gt;

&lt;p&gt;For a tax-related system, that is worse than a visible failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make every dependency explicit
&lt;/h2&gt;

&lt;p&gt;We created a source-contract matrix for the Shopify integration.&lt;/p&gt;

&lt;p&gt;For every operation, it records information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the GraphQL operation name&lt;/li&gt;
&lt;li&gt;the Shopify API version&lt;/li&gt;
&lt;li&gt;required scopes&lt;/li&gt;
&lt;li&gt;fields used by the application&lt;/li&gt;
&lt;li&gt;validation gates&lt;/li&gt;
&lt;li&gt;evidence limitations&lt;/li&gt;
&lt;li&gt;associated tests&lt;/li&gt;
&lt;li&gt;downstream consumers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns undocumented assumptions into something reviewable.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The order query gives us everything we need.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system has to answer more precise questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which order fields are required?&lt;/li&gt;
&lt;li&gt;Which fields are optional?&lt;/li&gt;
&lt;li&gt;What happens when Shopify returns partial GraphQL errors?&lt;/li&gt;
&lt;li&gt;Can a missing duties field be interpreted as zero?&lt;/li&gt;
&lt;li&gt;Which evidence is sufficient to classify a transaction?&lt;/li&gt;
&lt;li&gt;Which missing evidence must send the order to manual review?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Missing data is not the same as zero
&lt;/h2&gt;

&lt;p&gt;This was one of the most important design rules.&lt;/p&gt;

&lt;p&gt;Imagine that Shopify returns an order but cannot return duties or additional-fee information for part of the query.&lt;/p&gt;

&lt;p&gt;There are at least three possible meanings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The amount is genuinely zero.&lt;/li&gt;
&lt;li&gt;The field is unavailable.&lt;/li&gt;
&lt;li&gt;Shopify returned a partial error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Treating all three cases as zero creates false confidence.&lt;/p&gt;

&lt;p&gt;A safer model distinguishes between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confirmed_zero
known_value
unavailable
conflicting
needs_review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is useful far beyond VAT.&lt;/p&gt;

&lt;p&gt;It applies to payments, inventory, shipping, identity verification, analytics, and almost any integration where incomplete data can affect a business decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate the contract in CI
&lt;/h2&gt;

&lt;p&gt;Documentation alone eventually becomes outdated.&lt;/p&gt;

&lt;p&gt;The next step was making the contract machine-readable and validating it during CI.&lt;/p&gt;

&lt;p&gt;The validation checks for problems such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an operation missing from the inventory&lt;/li&gt;
&lt;li&gt;an undocumented validation gate&lt;/li&gt;
&lt;li&gt;malformed contract data&lt;/li&gt;
&lt;li&gt;unsafe file references&lt;/li&gt;
&lt;li&gt;claims that exceed the available evidence&lt;/li&gt;
&lt;li&gt;drift between queries, tests, and documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to prove that an external API will never change.&lt;/p&gt;

&lt;p&gt;The goal is to make our own assumptions visible and make accidental drift difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization must be tested as an attacker would use it
&lt;/h2&gt;

&lt;p&gt;A valid Shopify session token is only the beginning.&lt;/p&gt;

&lt;p&gt;An embedded application also needs to defend against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;forged tokens&lt;/li&gt;
&lt;li&gt;expired tokens&lt;/li&gt;
&lt;li&gt;tokens that are not valid yet&lt;/li&gt;
&lt;li&gt;wrong audience values&lt;/li&gt;
&lt;li&gt;tokens issued for another shop&lt;/li&gt;
&lt;li&gt;cross-tenant resource access&lt;/li&gt;
&lt;li&gt;resource enumeration&lt;/li&gt;
&lt;li&gt;mutation replay&lt;/li&gt;
&lt;li&gt;oversized request bodies&lt;/li&gt;
&lt;li&gt;unsafe proxy destinations&lt;/li&gt;
&lt;li&gt;sensitive information leaking into logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many authorization tests verify only the successful path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valid user + valid token + owned resource = success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more valuable tests cover combinations that must fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valid user + valid token + another store's resource = rejected
valid token + wrong audience = rejected
expired token + valid resource = rejected
replayed mutation = rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multi-tenant security is not complete until ownership is checked at every relevant boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks require a different trust model
&lt;/h2&gt;

&lt;p&gt;Webhook endpoints are public by design.&lt;/p&gt;

&lt;p&gt;That means their processing order matters.&lt;/p&gt;

&lt;p&gt;A safer flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the bounded raw request body.&lt;/li&gt;
&lt;li&gt;Verify the HMAC signature.&lt;/li&gt;
&lt;li&gt;Reject invalid requests.&lt;/li&gt;
&lt;li&gt;Parse the payload.&lt;/li&gt;
&lt;li&gt;Resolve the relevant integration.&lt;/li&gt;
&lt;li&gt;Apply idempotency.&lt;/li&gt;
&lt;li&gt;Persist minimal required information.&lt;/li&gt;
&lt;li&gt;Queue expensive processing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Parsing or resolving tenant data before signature verification creates unnecessary exposure.&lt;/p&gt;

&lt;p&gt;Webhook retries also mean that duplicate delivery is normal, not exceptional. Idempotency must therefore be part of the design from the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail closed, but preserve recoverability
&lt;/h2&gt;

&lt;p&gt;Failing closed does not have to mean permanently losing the transaction.&lt;/p&gt;

&lt;p&gt;When evidence is incomplete, the system can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retain a minimal diagnostic record&lt;/li&gt;
&lt;li&gt;place the transaction into a review queue&lt;/li&gt;
&lt;li&gt;allow a controlled replay&lt;/li&gt;
&lt;li&gt;run a reconciliation job later&lt;/li&gt;
&lt;li&gt;preserve the reason why automated processing stopped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provides a useful balance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do not silently accept uncertain data&lt;/li&gt;
&lt;li&gt;do not discard potentially recoverable data&lt;/li&gt;
&lt;li&gt;do not hide the decision from operators&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would do earlier next time
&lt;/h2&gt;

&lt;p&gt;If I were starting another integration today, I would define these before implementing the first production query:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A machine-readable inventory of external operations.&lt;/li&gt;
&lt;li&gt;Explicit trust boundaries for each response field.&lt;/li&gt;
&lt;li&gt;A distinction between zero, missing, unavailable, and conflicting data.&lt;/li&gt;
&lt;li&gt;Cross-tenant authorization abuse tests.&lt;/li&gt;
&lt;li&gt;Webhook idempotency and reconciliation.&lt;/li&gt;
&lt;li&gt;Evidence-based review states instead of optimistic defaults.&lt;/li&gt;
&lt;li&gt;CI checks that detect contract drift.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These decisions are much cheaper to make early than after customers depend on the integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;A production integration is not simply a connector between two APIs.&lt;/p&gt;

&lt;p&gt;It is a boundary between two systems with different data models, security assumptions, failure modes, and release cycles.&lt;/p&gt;

&lt;p&gt;The safest approach is to treat every external value as evidence that must be verified, classified, and preserved, not merely as JSON that successfully parsed.&lt;/p&gt;

&lt;p&gt;I am applying these principles while building the Shopify integration for VAT Engine, an EU VAT compliance platform. Shopify is the first native commerce connector, with additional ecommerce integrations planned on the same shared ingestion model.&lt;/p&gt;

&lt;p&gt;I would be interested to hear how other developers prevent external API contract drift in production integrations.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>api</category>
      <category>security</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
