GET /blog

Talking SOAP to the Spanish Tax Agency — and surviving

There is a particular kind of humility that only comes from integrating with a government API. You can be a perfectly competent engineer, ship REST services that hum along for years, and then meet a SOAP endpoint published by a tax agency and feel like a beginner again. This is the story of building an automated invoice pipeline against Spain's AEAT, and the habits it beat into me.

SOAP is not the problem — the contract is

People love to dunk on SOAP, and the XML envelopes really are verbose. But the envelope was never the hard part. The hard part is the WSDL: a contract written by people who will never read your code, that you must satisfy exactly, with no room to negotiate.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header/>
  <soapenv:Body>
    <SuministroLRFacturasEmitidas>
      <!-- one wrong namespace and the whole thing is rejected -->
    </SuministroLRFacturasEmitidas>
  </soapenv:Body>
</soapenv:Envelope>

Get a namespace wrong, send a date in the wrong format, omit a field the schema marks optional but the validator secretly requires — rejected. The error you get back is rarely the error that happened.

Validate before you send, not after they reject

The single biggest improvement to the pipeline was moving validation upstream. Instead of building a request, sending it, and parsing the rejection, I validated every invoice against the XSD locally before it ever touched the network.

If the schema can reject it, reject it yourself first — with an error message a human can actually act on.

This turned a class of opaque remote failures into clear local ones, caught in seconds instead of after a round-trip to a server that only answers during business hours.

Retries are a design decision, not an afterthought

The tax agency's servers go down. They go down for maintenance, they go down under load at filing deadlines, and sometimes they just time out for no reason you'll ever learn. A naive retry loop makes this worse — hammering a struggling server is how you get rate-limited.

  • Exponential backoff with jitter, so a fleet of clients doesn't retry in lockstep.
  • Idempotency keys, so a retried submission isn't filed twice.
  • A dead-letter queue, so a permanently failing invoice gets a human's attention instead of looping forever.

What it taught me

Defensive programming stops being a buzzword when the other side of the integration has no incentive to be forgiving. Assume every field can be malformed, every connection can drop mid-stream, and every success response should be double-checked. It's exhausting — and it's exactly the discipline that makes the rest of your code more robust too.

GET /blog ← back to all posts

Disagree with a post? Tell me. POST /contact → best conversations start with a code review

POST /contact →