“Convert the XML to JSON and expose an endpoint” is bad advice for a production SOAP to REST API migration. It produces a convincing demo, but it doesn't tell your checkout whether a tax identifier is invalid, a national service is unavailable, or your gateway failed before receiving an answer. Those distinctions matter when your application is deciding what to put on an invoice.
For VAT validation, the useful abstraction isn't a prettier transport. It's a boundary that preserves upstream meaning, gives checkout a predictable failure policy, and records what your billing system knew when it made a decision.
Table of Contents
- Why Converting SOAP to REST Is Not Just an XML to JSON Swap
- Inventory Your WSDL Contract Before Writing Code
- Designing Resource Endpoints and JSON Schemas
- Mapping SOAP Faults to Stable REST Errors
- Adding Auth, Caching, and Retries in Node.js and Python
- Testing Parity and Deciding Whether to Build or Buy
- Shipping Your Facade and Planning for E-Invoicing
Why Converting SOAP to REST Is Not Just an XML to JSON Swap
SOAP is a messaging protocol. REST is an architectural style. SOAP defines XML envelopes, extensible headers, and protocol bindings; REST organizes interactions around resources, representations, stateless requests, and HTTP semantics. That distinction, described in this comparison of SOAP and REST design approaches, is why replacing angle brackets with braces doesn't complete a migration.
A converter can preserve field values while losing the rules around them. It might turn a structured upstream fault into an empty JSON object, discard a security header, or represent an inconclusive lookup as false. The response parses correctly, but the application makes the wrong decision.

Preserve meaning before simplifying access
A useful facade translates several contracts, not just the payload:
- Operations into resources. A SOAP action becomes an intentional client-facing interaction, not automatically an identically named route.
- Schemas into representations. Required fields, missing values, and normalization rules remain explicit.
- WSDL into a published contract. Documentation or OpenAPI descriptions must explain both successful responses and failures.
- Operational guarantees into application policies. Authentication, retries, idempotency, and fault handling need deliberate ownership.
For billing integrations, simplifying access is still worthwhile. Magnitude Marketing's discussion of integrating business systems to reduce manual entry provides useful business context: the integration should remove repetitive work without forcing staff to resolve ambiguous automated decisions.
Keep the authoritative service behind the boundary
You don't need to rewrite the tax service to improve its interface. A facade can preserve the upstream system while isolating application code from its contract and failure behavior.
Public API history supports that interoperability approach rather than a blanket replacement strategy. The public API directory analysis discussed by The New Stack describes REST's prominence among public interfaces, while also cautioning against treating public-directory coverage as a census of enterprise systems.
A good facade makes uncertainty explicit. A bad wrapper makes uncertainty look like a negative answer.
The practical work starts with the upstream contract, then moves through resource design, error semantics, runtime safeguards, and verification. Invoice evidence, rather than JSON formatting, should determine whether the resulting interface is useful.
Inventory Your WSDL Contract Before Writing Code
Open the service definition before creating a route. Build a contract inventory that another engineer can use without reconstructing every assumption from the SOAP client implementation.
The inventory should combine the WSDL, imported schemas, service documentation, and observed responses. Where behavior isn't documented, mark it as unresolved rather than choosing a default.

Record each operation as a working contract
For every operation, capture the following:
- Identity and destination. Record its action, namespace, binding, endpoint, and downstream dependency.
- Request rules. List required fields, types, country conventions, optional values, and the distinction between omitted and empty input.
- Response meaning. Document each returned field, its availability, and whether missing company details change the validation outcome.
- Fault behavior. Capture declared faults and unexpected responses separately. Include redacted examples where available.
- Runtime assumptions. Record authentication, timeout ownership, retry eligibility, and whether repetition can create side effects.
A VIES-style lookup illustrates why this matters. The visible operation may accept a country and identifier, but the surrounding rules determine whether the caller receives an authoritative answer, an unavailable-country response, or an unclassified transport failure.
Don't infer idempotency from an operation's name. Ask whether repeating it only retrieves information or also creates a reference, submits a document, or changes upstream state. Document the answer before allowing an HTTP client or queue worker to retry automatically.
Identify guarantees that need explicit replacements
SOAP integrations may use WS-Security, reliable messaging, or transaction coordination. REST doesn't supply direct equivalents for those capabilities. The analysis of semantic challenges in SOAP modernization is particularly relevant when an apparently simple tax lookup depends on broader delivery and fault semantics.
| Existing concern | Facade design decision |
|---|---|
| Message security | Decide where TLS, OAuth, mTLS, or retained message signing applies |
| Reliable delivery | Define acknowledgement, deduplication, and retry ownership |
| Transaction coordination | Specify consistency boundaries and compensation behavior |
| Auditability | Preserve request identity, source outcome, and decision provenance |
These aren't interchangeable checkboxes. TLS protects a connection; it doesn't automatically replace an end-to-end message signature. Similarly, adding retries doesn't recreate transactional delivery.
Finish with an inventory that names an owner for every unresolved behavior. Endpoint design should follow those decisions, not conceal them.
Designing Resource Endpoints and JSON Schemas
Consider a validation as an evidence resource, not a remote procedure with a new URL. That choice changes what the client receives and what your billing system can later explain.
For a proposed contract, use POST /vat-validations when the application requests a validation record. Let GET /vat-validations/{reference} retrieve that existing record without performing another upstream lookup. These are illustrative routes, not claims about a particular provider's API.
Separate current lookup intent from recorded evidence
A read-only lookup can have its own retrieval interface if that suits your application. However, document freshness explicitly: retrieving an earlier result and requesting a fresh observation aren't the same operation.
Writes that affect billing state need a separate contract. Attaching validation evidence to an invoice, for example, shouldn't be an undocumented side effect of checking an identifier.
Wonderment Apps' discussion of building reliable integrations for SaaS platforms is useful context for keeping those client-facing responsibilities understandable. The practical test is whether a frontend or billing engineer can predict an endpoint's behavior without reading the SOAP adapter.
Specify the representation field by field
The following is a proposed schema outline. It deliberately separates source facts from facade metadata.
| Field | Proposed type and meaning |
|---|---|
validation_status |
Enum distinguishing valid, invalid, and inconclusive outcomes |
legal_name |
Nullable string containing available normalized company-name data |
address |
Nullable structured object, with original text retained separately when needed |
checked_at |
Timestamp of the source observation |
jurisdiction |
Country or tax jurisdiction used for the lookup |
source_status |
Enum describing whether the authoritative source answered |
validation_reference |
Opaque reference to the recorded evidence |
cache_status |
Indicates whether the response reused an earlier observation |
Nullable does not mean invalid. If a source doesn't supply a name or address, return a documented missing value rather than inventing details or changing the validation status.
Preserve the original source text alongside normalized fields where your retention policy permits it. Normalization should help invoice rendering, not erase evidence of what the source returned.
Version meaning, not just field names
Choose path-based or header-based versioning and document it consistently. Treat changes to status meanings, normalization rules, or freshness guarantees as contract changes, even if the JSON remains syntactically compatible.
A boolean is attractive because it's easy to consume. An evidence record is more useful because the caller can distinguish what was observed, when it was observed, and whether the source answered without guessing.
Mapping SOAP Faults to Stable REST Errors
An invalid identifier is a business outcome. A failed lookup is an operational outcome. Putting both behind the same false value forces checkout code to guess, usually at the worst possible moment.
The table below is a recommended facade policy, not a description of mandatory VIES or provider response codes. Keep it consistent across clients and document the distinction between a completed validation and a request that couldn't be completed.
Fault categories and REST responses
| Outcome | REST response | Retry behavior |
|---|---|---|
| Valid identifier | 200, validation_status=valid |
No retry needed |
| Authoritatively invalid identifier | 200, validation_status=invalid, reason vat_invalid |
Don't retry unchanged input |
| Malformed request | 400, invalid_request |
Correct input first |
| Missing or invalid credentials | 401, authentication_failed |
Repair credentials first |
| Authenticated caller lacks permission | 403, permission_denied |
Change authorization |
| Country system unavailable | 503, country_service_unavailable |
Bounded retry if eligible |
| Upstream timeout | 504, upstream_timeout |
Retry only within the remaining budget |
| Gateway cannot interpret upstream response | 502, upstream_protocol_error |
Investigate; avoid automatic loops |
| Classified transient upstream server fault | 503, upstream_transient |
Bounded retry for eligible operations |
| Duplicate write with matching idempotency record | Replay the recorded response | Don't execute again |
| Idempotency key reused with different input | 409, idempotency_conflict |
Submit a corrected request |
| Recognized business SOAP fault | 422, documented domain-specific code |
Retry only if that category permits it |
| Unclassified facade failure | 500, internal_error |
Don't assume repetition is safe |
Keep client decisions independent of SOAP wording
Return a stable machine-readable code, a concise message, and a correlation reference. Don't make clients search English fault strings for words such as “unavailable.”
The REST API response-code guidance provides a useful companion when documenting that contract. Whatever convention you choose, use status codes to describe the request outcome and structured fields to explain the tax-validation result.
A national backend failure must remain scoped to that jurisdiction. Because VIES is federated, an unavailable member-state service doesn't establish that every country is unavailable, and it certainly doesn't establish that the submitted identifier is invalid.
Decision boundary: An inconclusive lookup should trigger your documented checkout policy, not silently become a tax conclusion.
Internally, retain the correlation ID, original fault category, affected jurisdiction, and adapter classification. Restrict raw payload access and redact credentials before logging. Support engineers need enough context to distinguish a source outage from a parser regression without exposing unnecessary customer data.
Adding Auth, Caching, and Retries in Node.js and Python
Keep the SOAP adapter small, but don't make the surrounding policy accidental. Both language implementations should follow the same sequence: bounded input validation, authentication, authorized cache access, upstream execution, error normalization, and response serialization.
Use HTTPS at the ingress and secure the ingress-to-application boundary. The OWASP REST Security Cheat Sheet requires HTTPS and recommends testing malformed tokens so authentication failures don't become server errors or partial parses.

Keep equivalent handler boundaries
These illustrative handler cores assume injected, tested helpers. They're not standalone servers: authenticate, cache, retryRead, and normalizeError must implement the policies described below.
Node.js
async function validateVat(req, res) {
try {
const input = parseBoundedInput(req.body);
const caller = await authenticate(req.headers.authorization);
const key = cacheKey(caller.scope, input);
const cached = await cache.get(key);
if (cached) return sendEvidence(res, cached, "hit");
const result = await retryRead(
() => soap.lookup(input),
req.deadline
);
const evidence = normalizeResult(result);
if (cacheable(evidence)) {
await cache.set(key, evidence, cachePolicy.ttl);
}
return sendEvidence(res, evidence, "miss");
} catch (error) {
return sendError(res, normalizeError(error));
}
}
Python
async def validate_vat(request):
try:
data = parse_bounded_input(await request.json())
caller = await authenticate(request.headers.get("authorization"))
key = cache_key(caller.scope, data)
cached = await cache.get(key)
if cached is not None:
return evidence_response(cached, "hit")
result = await retry_read(
lambda: soap.lookup(data), request.deadline
)
evidence = normalize_result(result)
if cacheable(evidence):
await cache.set(key, evidence, cache_policy.ttl)
return evidence_response(evidence, "miss")
except Exception as error:
return error_response(normalize_error(error))
Implement policies outside the handlers
- Authentication. Reject malformed tokens as authentication failures. Don't let token parsing exceptions fall through to an internal-error response.
- Redis caching. Store completed, eligible observations with an explicit TTL. Include jurisdiction, canonical identifier, and relevant contract version in the key; include tenant scope where records contain tenant-specific data.
- Retry control. Retry only classified transient failures, using exponential backoff with jitter, an attempt cap, and an overall deadline.
- Write protection. Reserve idempotency keys before executing retried writes. Bind each key to the caller and request fingerprint, then persist the outcome.
Cache age and checkout tolerance are different policies. A cached result can be technically available while no longer acceptable for a particular invoice decision.
Use the VIES downtime resilience guidance when defining circuit-breaker and outage behavior. If stale evidence is permitted, label it explicitly and preserve its original observation time; otherwise require revalidation or route the transaction to review.
Testing Parity and Deciding Whether to Build or Buy
A successful HTTP response isn't proof of a successful migration. The acceptance criterion is whether the new interface preserves meaning under ordinary traffic, malformed input, source failures, and repeated requests.
Build a golden fixture set that pairs redacted SOAP inputs and responses with the expected REST representation. Compare semantic outcomes, not serialized bytes: normalization can legitimately change formatting without changing meaning.

Make cutover depend on evidence
Contract tests should validate response schemas and error codes. Negative tests should cover malformed tokens, missing fields, unexpected namespaces, empty upstream bodies, and interrupted connections.
Use parallel shadow traffic before changing invoice decisions. For read-only operations, compare the existing and proposed paths while keeping the existing integration authoritative. Don't shadow side-effecting operations unless duplication is explicitly prevented.
Measure p50, p95, and p99 latency, throughput, CPU, memory, timeout rate, and payload bytes under representative concurrency. Separate cache hits from upstream calls so a favorable cache mix doesn't conceal a slow miss path. The published SOAP and REST performance experiments reinforce the need for workload-specific measurement rather than assuming a protocol label guarantees an improvement.
Don't approve cutover because the average response looks faster. Approve it when the slow path, failure path, and invoice outcome meet your acceptance criteria.
Compare ownership rather than syntax
Building in-house makes sense when you need unusual security constraints, specialized evidence retention, or tight control over upstream interpretation. The cost isn't just the initial adapter. Your team owns schema drift, outage classification, certificate handling, dependency updates, and production support.
A managed validation API is worth evaluating when that operational work doesn't differentiate your product. The publisher's managed option exposes a REST interface over VIES, applies country-specific format checks before remote requests, uses Redis-backed caching, and returns machine-readable categories such as vat_invalid and service_unavailable.
Evaluate any provider against the same fixture set you use for an internal implementation. Ask how it distinguishes authoritative invalidity from unavailable sources, whether company details can be absent, how cached observations are identified, and what evidence you can export.
A provider can remove adapter ownership without removing your billing responsibility. Your application still decides whether to continue checkout, hold an invoice, or request manual review. Keep that policy in code you control rather than relying on undocumented vendor behavior.
Shipping Your Facade and Planning for E-Invoicing
Ship a narrow, observable slice rather than replacing every integration path at once. Start with an operation whose behavior you understand and whose outcomes you can compare without changing invoice treatment.
Before enabling it for customer decisions, require a short release checklist:
- Contract inventory approved. Unresolved upstream behavior has a named owner.
- Error map exercised. Each category produces the intended application action.
- Cache policy documented. Freshness, stale-result eligibility, and invalidation are explicit.
- Retry budget bounded. Callers can't multiply retries across application layers.
- Parity suite passing. Fixtures, negative tests, and shadow comparisons meet acceptance criteria.
- Evidence accessible. Support and finance can retrieve the relevant observation without unrestricted payload access.
Design for the invoice lifecycle
Validation belongs in an evidence pipeline alongside customer details, invoice data, and the rules used to determine tax treatment. A successful lookup can support invoice rendering, but it doesn't by itself establish every condition for applying reverse charge.
The EU's VAT in the Digital Age package entered into force on 14 April 2025, and mandatory cross-border B2B e-invoicing and digital reporting are scheduled from 1 July 2030, according to the European Commission's ViDA overview. Those scheduled requirements make schema evolution and retention planning relevant beyond the initial migration.
The practical connection is covered in the e-invoicing API integration guide. Keep validation observations separate from later invoice decisions so you can explain both without pretending they happened at the same time.
Assign ownership beyond the adapter
When the work crosses checkout, accounting, and internal operations, resources such as custom integrations by Continuum Solutions can help frame the wider integration scope. Regardless of who implements it, assign responsibility for changes to billing rules and evidence retention, not just endpoint availability.
This week, wrap a single SOAP operation, replay representative fixtures, and compare the resulting evidence with your current integration. Delay routing real invoice decisions through it until the inconclusive cases are as well understood as the successful ones.
TaxID offers a REST interface for VAT validation with format checks, caching, and machine-readable failures, providing an alternative to maintaining your own SOAP adapter. Visit TaxID to try the free tier, compare representative results with your existing integration, and check whether its response semantics fit your checkout and invoice policies.