DEV Community

Vasyl Kyryliuk for VAT Engine

Posted on

What Building a Shopify Integration Taught Me About Trusting External APIs

Building a Shopify integration looks straightforward at first:

  1. Authenticate the store.
  2. Request orders.
  3. Transform the response.
  4. Store the result.

The difficult part begins when the integration needs to become reliable enough for production.

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.

External APIs are contracts, not just endpoints

A GraphQL query can continue returning a successful response while the meaning of the returned data changes around it.

An integration depends on more than the endpoint itself:

  • API version
  • requested fields
  • required access scopes
  • pagination behaviour
  • nullable fields
  • partial GraphQL errors
  • protected customer-data permissions
  • webhook topics
  • authentication requirements
  • assumptions made during transformation

That entire combination is the real contract.

If any part changes silently, the integration may continue running while producing incomplete or misleading results.

For a tax-related system, that is worse than a visible failure.

Make every dependency explicit

We created a source-contract matrix for the Shopify integration.

For every operation, it records information such as:

  • the GraphQL operation name
  • the Shopify API version
  • required scopes
  • fields used by the application
  • validation gates
  • evidence limitations
  • associated tests
  • downstream consumers

This turns undocumented assumptions into something reviewable.

Instead of saying:

The order query gives us everything we need.

The system has to answer more precise questions:

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

Missing data is not the same as zero

This was one of the most important design rules.

Imagine that Shopify returns an order but cannot return duties or additional-fee information for part of the query.

There are at least three possible meanings:

  1. The amount is genuinely zero.
  2. The field is unavailable.
  3. Shopify returned a partial error.

Treating all three cases as zero creates false confidence.

A safer model distinguishes between:

confirmed_zero
known_value
unavailable
conflicting
needs_review
Enter fullscreen mode Exit fullscreen mode

This pattern is useful far beyond VAT.

It applies to payments, inventory, shipping, identity verification, analytics, and almost any integration where incomplete data can affect a business decision.

Validate the contract in CI

Documentation alone eventually becomes outdated.

The next step was making the contract machine-readable and validating it during CI.

The validation checks for problems such as:

  • an operation missing from the inventory
  • an undocumented validation gate
  • malformed contract data
  • unsafe file references
  • claims that exceed the available evidence
  • drift between queries, tests, and documentation

The goal is not to prove that an external API will never change.

The goal is to make our own assumptions visible and make accidental drift difficult.

Authorization must be tested as an attacker would use it

A valid Shopify session token is only the beginning.

An embedded application also needs to defend against:

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

Many authorization tests verify only the successful path:

valid user + valid token + owned resource = success
Enter fullscreen mode Exit fullscreen mode

The more valuable tests cover combinations that must fail:

valid user + valid token + another store's resource = rejected
valid token + wrong audience = rejected
expired token + valid resource = rejected
replayed mutation = rejected
Enter fullscreen mode Exit fullscreen mode

Multi-tenant security is not complete until ownership is checked at every relevant boundary.

Webhooks require a different trust model

Webhook endpoints are public by design.

That means their processing order matters.

A safer flow is:

  1. Read the bounded raw request body.
  2. Verify the HMAC signature.
  3. Reject invalid requests.
  4. Parse the payload.
  5. Resolve the relevant integration.
  6. Apply idempotency.
  7. Persist minimal required information.
  8. Queue expensive processing.

Parsing or resolving tenant data before signature verification creates unnecessary exposure.

Webhook retries also mean that duplicate delivery is normal, not exceptional. Idempotency must therefore be part of the design from the beginning.

Fail closed, but preserve recoverability

Failing closed does not have to mean permanently losing the transaction.

When evidence is incomplete, the system can:

  • retain a minimal diagnostic record
  • place the transaction into a review queue
  • allow a controlled replay
  • run a reconciliation job later
  • preserve the reason why automated processing stopped

This provides a useful balance:

  • do not silently accept uncertain data
  • do not discard potentially recoverable data
  • do not hide the decision from operators

What I would do earlier next time

If I were starting another integration today, I would define these before implementing the first production query:

  1. A machine-readable inventory of external operations.
  2. Explicit trust boundaries for each response field.
  3. A distinction between zero, missing, unavailable, and conflicting data.
  4. Cross-tenant authorization abuse tests.
  5. Webhook idempotency and reconciliation.
  6. Evidence-based review states instead of optimistic defaults.
  7. CI checks that detect contract drift.

These decisions are much cheaper to make early than after customers depend on the integration.

Final thought

A production integration is not simply a connector between two APIs.

It is a boundary between two systems with different data models, security assumptions, failure modes, and release cycles.

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.

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.

I would be interested to hear how other developers prevent external API contract drift in production integrations.

Top comments (0)