Thoa Tran Back to selected work
Final testing before go-live Lending · Platform scope · Jun – Sep 2026

From one lender to a lender-agnostic platform

Brokers could submit personal loans to one partner lender. When a second lender arrived, with no screens of its own and an API-only integration, the easy path was a second one-off build. I led the work: I designed the first flow from scratch with our partner lender's team, then turned it into a pattern that any lender can plug into, and scoped the second lender's first phase to about a week.

My role
Led the project: scope, phases and milestones, requirements, design and QA.
Team
Our engineers, plus the partner lender's team and its lead. Lender contracts sat with my manager.
Timeline
June to September 2026, in phases.
Users
Mortgage brokers and advisers submitting loans for their clients.

Every new lender looked like a new project

By mid-2026 I had worked on this platform for more than two years, so I knew where brokers lost time long before this project started. Brokers use the platform to prepare a client's loan and send it to a lender. The first integration was a personal loan flow with our partner lender. Then a second lender signed on. It had no interface for brokers at all: it would only receive data through its API and manage everything on its own side.

Building a second, separate submission flow would have worked once. It would also have meant a third build for the third lender, and brokers learning a different flow for each one. The real question was what stays the same across lenders, and what has to change.

Designing the first flow from scratch

The personal loan flow went through nine iterations before it shipped. These are the turning points that later made it reusable.

Early versionsMap the broker's job

A story map from pre-approval to quote, client confirmation and the broker's final check.

Mid versionsName sections by what to do

“Action needed” and “Earlier submissions” instead of system states, so brokers see their next step first.

Later versionsOne layout for every wait

Action on the left, timeline on the right, on every waiting state.

FinalRemove dead ends

Conditions advance on their own to identity checks, and processing shows progress instead of a frozen screen.

I ran the project with the partner lender's team and its lead. Together we defined both directions of the integration: what application data goes from our platform to the lender, and what comes back, the lender's status and any action the broker needs to take. I set the phases and milestones and wrote the requirements each one shipped against.

The pattern that travelled

Action left, timeline right

Loan applications spend most of their life waiting: for the client, the lender or a document. I gave every waiting state the same layout. The left side says what the broker can do now. The right side shows where the application is and what happens next.

Because the layout describes waiting in general, not one lender's steps, it carried straight over to the second lender.

I built the flow as a 15-screen clickable HTML prototype, so product, engineering and QA could agree on one version before any build.

Action neededClient hasn't confirmed the quote yet.Resend to client
  • Pre-approval received
  • Quote sent to client
  • Awaiting client confirmation
  • Identity check
  • Submitted to lender
Rebuilt waiting state, sample data

A second lender, and two ways to add it

Dropped

A submit option inside the applications list

Fast to sketch, and it was my first idea. But that list's status column already tracked bank statement processing. Adding lender progress there would give one column two meanings, and home loan and personal loan work would mix.

Chosen

One Loans module for every lender

Personal loans and the new lender live in one module. Brokers start the same way every time and only choose the lender at one step. The flow reuses the personal loan components with each lender's fields.

New application→ Manage for client, or refer→ Select lender→ Lender's steps→ Review and submit

For the second lender, the lender's steps became a five-step form built from existing components:

Personal detailsLoan detailsProperty detailsDocumentsReview and submit

Adding a lender now means adding one option to “Select lender” and mapping its fields. Brokers don't learn a new flow.

Two statuses, not one

The decision I'd defend hardest is a data model one that shows up in the interface. An application has its own state in our product: drafted, documents in, submitted. A lender has a separate state on its side: under review, awaiting information, approved, declined.

Merging them into one status column looks simpler and breaks the moment one application goes to two lenders. So the Loans list keeps Application status and Lender status as separate columns, with a Lender column beside them.

ClientLenderApplication statusLender status
J. SamplePartner lenderSubmittedApproved
A. ExampleSecond lenderSubmittedAwaiting information
R. DemoSecond lenderDraft—
Rebuilt Loans list, cropped, sample data

Lender status uses one shared vocabulary that each lender's own states map onto:

NewSubmittedUnder reviewAwaiting informationApprovedCompletedDeclinedUnwanted

Agree the contract before anyone designs a screen

The second lender's API was still being finalised. If engineers built the form and mapping against a guessed payload, every change on the lender's side would mean rework on ours, twice. So I broke the work into four tickets and put a gate in front of the UI.

Build the submission API and field mappingEngineering
Confirm the API contract with the lenderGate
Broker completes the application formBlocked by gate
Broker tracks the lender's statusBlocked by gate

This is the gate ticket as I wrote it, with names removed.

Confirm lender API contract before building the submission UI
TaskPriority: HighBlocks: 2 stories

Context

We're connecting the Loans flow to a lender that only accepts data by API. The form, the mapping and status tracking will all be built against this payload. A change after build means rework in all three.

Acceptance criteria

  • Create and submit payloads are agreed with the lender in writing, field by field, against our field dictionary.
  • Every required lender field maps to data we already collect. Gaps are listed, not built, in Phase 1.
  • Sample requests pass against the lender's test endpoint for create, submit and status.
  • The lender's status values and their meaning are agreed and mapped to our lender status list.
  • Document handling is agreed: sent as attachments, with allowed file types and sizes.

Out of scope

  • New data fields in the application form
  • Sending one application to several lenders

Done when

My manager signs off the contract, and the two UI stories are unblocked.

The second lender only needed some of our fields. We sent our standard payload anyway: borrower details, loan details and the adviser's contact information. The lender takes what it needs, and the next lender gets the same payload, so its integration starts from mapping, not from a new build.

The lender's first request was a short email listing the fields it wanted. I checked each one against our data model, decided what we could send and what was out of scope, and wrote it up as a field dictionary. Engineering built the API from that document, adjusting the technical structure where they needed to, but the scope and field meanings stayed the ones I had set. I had done the same for personal loans earlier: I wrote the base payload, and engineering built on it.

That dictionary became the master: every lender field, what it means, and where our data for it comes from. It turns “can we support this lender?” into a lookup instead of a meeting.

Scoping Phase 1 to about a week

The fastest way to learn whether the pattern held was to ship a thin version. Phase 1 used only data the platform already collects, added no new fields, and sent documents to the lender as attachments.

That made the first submission less complete than it could be. It also meant the work was mapping and reuse, not new design and new database fields, so it fitted in about a week instead of a quarter.

Decisions I'd defend, and what they cost

One Loans module instead of a lender-specific flow

Brokers learn one flow; a new lender is one more option.

Each lender's form has to fit a shared structure, even when its fields are unusual.

Application status and lender status as separate fields

Ready for one application going to several lenders.

One more column in the list, and more to explain at first.

Contract gate before UI work

Engineers never build against a payload that might change.

UI work waits on the lender's timeline, not ours.

A standard payload, not each lender's minimum

One payload every lender receives, so the next integration is mapping only.

More data leaves our system than one lender strictly needs, so the standard set stays limited to what lenders assess an application on.

Phase 1 with existing data only

Proves the pattern in about a week.

A thinner first submission; richer data waits for Phase 2.

Dropping my own first idea

The applications-list option mixed two meanings of status.

Sketches and a review cycle I'd already spent.

Outcome

LivePersonal loan submission to the partner lender, with status and actions syncing back, through the project I led.
~1 weekPhase 1 scope for the second lender, reusing the existing flow and data.
Go-liveThe second lender's API agreement is in place, and the end-to-end flow is in final testing before go-live.
1 flowfor every lender, with one shared lender status list.

What I'd do next

  • Pass brokers' uploaded documents, such as payslips, through to the lender, so lenders get the full picture on the first submission.
  • Let one application go to several lenders and compare their responses side by side, which the two-status model already supports.
  • Track time from first draft to submission per lender, to see where brokers wait and why.

What I learned: the second integration is where a product decides whether it's building features or a platform. Most of the leverage came from deciding what to keep the same, not from what I added.