← Go Pivot Edge · work
CASE · MULTI-TENANT CLIENT PORTAL

Every client gets a login.
Nobody gets a PDF.

An automated reporting pipeline and a multi-tenant client portal for a lead generation agency running paid campaigns for insurance and home services brands — where the spend, the leads and the truth all live in different systems.

CLIENTlead generation agency (unnamed) · insurance & home servicesBUILD reporting pipeline · client portalSHAPE multi-tenant, one login per client

→ You’re already signed in as Halcyon Insurance Group. Flip identities on the portal and watch what changes — and what you can no longer see.

CLIENT PORTALSIMULATED · FICTIONAL CLIENTS & FIGURES
DEMO IDENTITIES · one tap, no real auth
auth ok · session bound to t_hal · scope=read:own
Halcyon Insurance Group
signed in as j.okafor · client since NOV 2024
SCOPE · you are seeing Halcyon Insurance Group’s data only · tenant=t_hal · 2 other tenants exist on this system and are not visible to this session
SPEND · MTD
$29,760
CONVERSIONS
587
BLENDED CPA
$50.70
CAMPAIGNS LIVE
2 / 3
CAMPAIGNS
  • CMP-011 Search — auto & home quotes · Searchlive
    spend $18,420conv 312cpa $59.0412-week performance index for CMP-011, from 59 to 106
  • CMP-014 Retargeting — quote abandoners · Programmaticlive
    spend $9,130conv 244cpa $37.4212-week performance index for CMP-014, from 51 to 98
  • CMP-017 Display — life & term, batch C · Displaymigrating
    spend $2,210conv 31cpa $71.2912-week performance index for CMP-017, from 49 to 8
THE BOUNDARY, DEMONSTRATED

This session carries t_hal. Ask the API for any tenant’s summary — or for no tenant at all — and see what comes back:

— no request sent yet
REPORT DELIVERIES · YOUR ACCOUNT
  • 01 JUL 2026 06:00Monthly report — JUNdelivered · opened 09:12
  • 12 JUN 2026 07:35Ad account migration noticedelivered · opened 07:58
  • 01 JUN 2026 06:00Monthly report — MAYdelivered · opened 10:31
  • 01 MAY 2026 06:00Monthly report — APRdelivered · opened 08:44
01 · THE SITUATION

The client is a lead generation agency. They run paid campaigns for insurance and home services brands — quote forms, call-tracked leads, booked jobs. The agency isn’t named here at their preference, so take them as the agency throughout. The detail that matters: their numbers could never live inside any one ad platform. Spend sits in the ad accounts, the leads sit in call tracking and each brand’s CRM, and whether a lead turned into a booked job is a third system entirely. No platform holds the whole sentence.

02 · WHAT WAS ACTUALLY BROKEN

Monthly reporting was a person exporting from several ad accounts, pasting into a spreadsheet, making it presentable, and emailing each client a PDF. Once a month, per client. The PDF was stale the moment it sent, and every mid-month “quick question — how are we doing?” replayed the whole export dance in miniature. Worse: when a brand’s campaigns moved to a new ad account — a rebuild, a migration, a client arriving from a previous agency with half their history behind someone else’s login — the spreadsheet’s lineage quietly broke. Nobody decided to have a reporting problem. It accreted.

03 · WHAT I BUILT

Two things pretending to be one. First, a pipeline that pulls spend and performance from every channel account daily, normalises it, and stores it keyed by client, not by ad account — so when a brand’s campaigns move to a new account, the client’s history stays continuous. Second, a portal on top: each of the agency’s clients has their own password and sees their own campaigns, spend, and delivery history, live, whenever they want. The agency gets an admin view across everyone. The monthly report still goes out — it generates itself now — but it’s a bookmark, not the product.

The portal at the top of this page is the shape of that build, with fictional clients and fictional figures. Sign in as each of them. The dashed amber rule on Vantage’s local services campaign marks the week that campaign moved to a new ad account — the history reads as one line, which is the whole point of keying by client.

04 · THE BORING PART, WHICH IS THE REAL PART

Multi-tenancy is mostly a data-model problem wearing a UI costume. The dashboard was the quick bit. The actual work is making it structurally impossible for client A to ever receive client B’s row: every table carries a tenant id, every query goes through a layer that refuses to run without one, and there is no getAllCampaigns()for the client-facing side to call by accident. Deliberately boring. The scariest bug in a system like this isn’t a crash — a crash is loud. A cross-tenant leak is silent, and on a roster where two clients sell the same policy or the same roof in the same metro, a client seeing a competitor’s spend is not an “oops” email. So the scope check lives at the query layer, not the page layer, and the 403 in the demo above is the honest shape of it. So is the 400 — a query with no tenant attached doesn’t return everything, it returns nothing.

What I’d do differently: build the delivery log first, not last. It sounds like an afterthought until a client says “we never got June’s report” and the answer is a generation timestamp and the minute their session opened it. That log has ended more disputes than any chart on the board.

05 · WHAT CHANGED

The mid-month emails stopped. Nobody asked clients to stop sending them — “how did last month go” is just a question you only ask when you can’t see. What replaced those emails is narrower and better: why is this campaign’s CPA drifting, should budget move off the paused one, what happened in week three. Those are questions an agency actually wants to be asked, because they’re about the work rather than about the paperwork.

And the monthly report stopped being a thing anyone assembles. It still lands on the first, on time, in every client’s inbox — no one opens a spreadsheet to make that happen any more. It’s a reminder that the portal exists, which is about the right amount of importance for a PDF.

If your clients ask you for numbers you have to go and assemble, that’s a pipeline waiting to exist.

Tell me where your reporting lives right now — even if the answer is “four exports and a prayer”. I’ll tell you what the portal version looks like.

Go Pivot Edge · built by Arsalan