Context
A consumer lending business was sending the same loan application to seven providers by hand. A staff member re-keyed the applicant’s details into seven different portals, waited, and phoned the customer back — usually the next day, sometimes the day after.
By then the customer had applied somewhere else.
The problem
Every lender’s API was different, and all of them were bad in their own particular way.
Two were clean REST with sane JSON. One was SOAP, and its WSDL didn’t match what the endpoint actually returned. One accepted a POST and then delivered its decision by webhook forty seconds later. One rate-limited at a level nobody had documented. Two returned HTTP 200 on failure with the actual error buried in a string field.
None of them agreed on what an ID number field was called, how income should be formatted, or what “approved” meant.
What I did
I built a normalisation layer first, before touching a single integration. One internal application schema, and seven adapters that translate to and from it. Adding an eighth lender is now a new adapter class and a config entry — about a day’s work, not another integration project.
Each lender call runs in its own queued job, so one slow provider can’t hold up the other six. Anything asynchronous is reconciled by webhook against an idempotency key, which means a duplicate callback is a no-op rather than a duplicate loan offer.
The part that mattered most was the failure handling. Every request and response is logged with a correlation ID. When a lender changes their contract without telling anyone — which happened twice during the build, and has happened since — the alert names the lender, the field, and the payload that broke. Debugging is reading one log line, not reproducing a customer’s application by hand.
I also said no to two things the brief asked for: a scoring engine that would have duplicated what the lenders already do, and a caching layer for decisions, which would have been a compliance problem waiting to happen.
The result
One form, submitted once. The full panel of seven lenders answers in about four seconds, and the applicant sees ranked, real offers on the same page they applied on.
The re-keying job no longer exists. The system has run at 99.95% uptime since launch, and the two lender contract changes that broke it were diagnosed and patched inside an hour each — because the logging was built before the features.

