SaaS and technology · Data per customer

Data delivered to the SaaS's customers

ROI reports, a portal and extracts for every customer of your SaaS, all coming from a single model, isolated per customer and with numbers the customer can verify.

UsageWidely used
EffortMedium
ForSaaS companies that need to prove value to each customer or hand them their own data.
Source

Multi-tenant product database, GA4 and customer spreadsheets

Rule

One model parameterized by tenant, with an attribution window and estimated flags

Outcome

Value metrics per tenant, isolated and with no sensitive data

Activation

Portal, monthly report, extract and AI agent

What it solves

  • Every customer gets the same value calculation, without a manual query per request.
  • No customer sees another customer's data, or any integration credential.
  • The delivered number says whether it is measured, estimated or missing, and the customer can verify it.

Business questions it answers

Ask your agent these questions once the case is live.

How much revenue did our tool generate for each customer last month?
Which customers saw results drop from the previous month and need a CS conversation?
How much has the customer saved since starting to use the automation?
Do products with our feature sell more than those without it?
Which customers have a stalled integration and therefore show no results?
How long does it take to put a new customer on the portal?

What you need

  • Product database connected to Nekt, with the tenant id in every table or reachable by key
  • A per-tenant configuration table: id, destination layer, start date and active sources
  • The value rule agreed with customers: attribution window, baseline and rate

What you get

  • Per-tenant tables, isolated by layer and with no sensitive columns
  • A monthly value report with the closed month, comparison and alerts
  • A portal or extract to the customer's database fed by the same model
  • An AI agent that answers per customer without mixing data

Where to activate

Customer portal

Sync each tenant's layer to the portal database (Postgres, Supabase or S3) and show the value type next to the number.

Monthly value report

One row per tenant with the closed month, comparison and alerts, which CS takes to the results meeting.

AI agent via MCP

Answers CS and the customer about one tenant at a time, with the window and estimated flags already applied.

Ready to build?

The calculation is simple; the work is parameterizing by tenant and agreeing on the value rule. After that, each new customer is one configuration row. The detailed view has the business rules, the pitfalls and the acceptance test. Or send everything straight to your agent with the Nekt MCP.

The problem

SaaS customers ask for their own data and proof of value, and each request turns into a query on the production database or a copy of the transformation with the customer's id hard-coded. Some workspaces have hundreds of near-identical copies, and a rule change has to be repeated in all of them. Meanwhile, the value metric has details that change the number: attribution window, timestamps truncated by the integration, days with no measurement and missing rates.

How it is usually done, and the rule

Each row is a mistake that already happened in practice.

The common wayThe rule
01
Duplicating the transformation and swapping the customer's literal id in each copy.
One parameterized model, not one copy per customer

Write the transformation once with the tenant id as a column and read the tenant list from a configuration table (id, display name, destination layer, start date, active sources, parameters such as window and rate). A new customer is one row in that table. Copies are only justified when the customer has its own source that doesn't exist for the others, and even then they call the same calculation.

02
A single table with every customer and a filter on the dashboard.
The layer is the permission boundary

On Nekt, permissions are granted per layer. Deliver each customer in its own layer (or its own destination) and never in a shared table filtered only at read time. The consolidated table with every tenant stays in an internal layer, visible only to the SaaS team.

03
Copying entire child tables because they have no tenant column.
Filter by tenant and propagate through keys

Tables that have the tenant id are filtered directly. Child tables without that column (items, messages, insights) come in through the foreign key of their parent table; reference tables come in with only the rows the tenant's records use. Check that every copied table has a path to the tenant.

04
Copying the whole table from the product database into the customer's layer.
Remove sensitive columns before delivering

Keep a closed list of columns that never leave: client_secret, access_token, refresh_token, passwords, API keys and integration credentials. Exclude them from every delivered table, including those from standard connectors, such as the Stripe payment intent's client_secret.

05
Summing every order from the contact in the month as the tool's revenue.
Attribution with a declared window

Revenue attributed to a message, campaign or automation counts only orders from the same end customer placed after the send and within the agreed window (one real case uses 3 days). Each order is attributed to a single send, the most recent one before it. Amounts in cents are divided by 100 once.

06
Comparing an order at midnight with a message sent at 3 p.m. and losing same-day attribution.
Detect truncated timestamps per tenant

Some order integrations send only the date, with time 00:00:00. For each tenant, look at the last three orders: if all have zeroed hour, minute and second, treat the field as a date and compare the window in days; otherwise, as a timestamp. Store the decision in the tenant's configuration.

07
Writing zero savings when there is no rate.
No data is null, with the reason

If an input is missing (rate not registered, meter offline, stalled integration), the result is null with a reason column, never zero. Distinguish a data error from a deliberate decision: a customer who only contracted kWh metering has null savings in reais and no alert, not an incomplete-rate alert.

08
Filling gaps with a fixed value and delivering it as real data.
Estimated shows as estimated

Days with no reading can be filled with the average for the same weekday in the nearest real period, but the row carries a flag (measured or projected). Never use a fixed fill value. The savings baseline is the per-weekday average over the monitoring window before the automation; if operations change (for example, the number of devices), open a new baseline regime with a cutoff date.

09
Matching by name with contains and counting the same item in two groups.
With and without the feature, from the same catalog

To prove a feature's impact, split the customer's items into with and without it based on the list the customer maintains, and compare views, cart, purchase and revenue over the same period. The match is by item id; with no id, by a name that starts with the registered name, keeping a single match, the most specific one (longest name).

10
Building the report from derived tables and trusting the table name.
Read the application's raw tables and document the misleading ones

When the product writes its own derived tables (prefixes such as dw_ or dwt_) alongside the application tables, use the application tables, which are the source of truth. Record in the context document the tables whose names mislead (for example, a table called credit that stores charges), filter soft deletes (deletedAt null) and drop from the report any metric whose status is never filled in at the source.

How each delivered number is flagged

Every row delivered to the customer carries the value type, so the portal and the agent can explain the number.

Real reading or record in the periodMeasuredtipo_valor = medido
Gap filled with the average for the same weekdayProjectedtipo_valor = projetado
Input missing due to an error: rate, meter, integrationNo data, with alertnull value, reason filled
Input missing by contract choiceNot applicablenull value, no alert
Order outside the attribution windowNot attributedexcluded from attributed revenue
Integration with no new order for N daysStalled integrationnon-comparable result

Definitions

Tenant
a customer of the SaaS and the unit of isolation; one row in the configuration table with id, destination layer, start date, sources and parameters
Attributed revenue
sum of the tenant's order amounts placed after a send and within the window (e.g. 3 days), each order attributed to the most recent send, in reais, by tenant and month
Delivery rate
messages with status sent, delivered or read / messages dispatched, by campaign and channel
Connection rate
calls with status connected / completed calls (excludes preparing and in progress), by tenant and period
Influence rate
influenced delivered orders / reachable base (contacts with a previous order or conversation, with no campaign opt-out), by tenant and month
Savings
(per-weekday baseline minus actual consumption) x time-of-use rate, by tenant and day; null when the rate is missing
Monthly value report
the previous closed month against the month before it, with percentage delta and alerts, one row per tenant
Feature impact
views, cart, purchases and revenue of items with the feature against those without, in the same period and tenant

Pitfalls that already happened

Hundreds of copies

One transformation per customer grew to hundreds of versions; a rule fix left customers with different numbers for the same calculation.

Parameterize by tenant and migrate the copies to the single model.

Connector prefix hard-coded

When each connected source generates tables with the connector id in the name, the id changes between workspaces and when the source is reconnected; queries with a hard-coded id break.

Resolve the current prefix by listing tables before querying, and never write the id into the context document.

History wiped by a full sync

A full sync from the source deleted months of raw data that no longer existed in the API, and the value report lost its history.

Keep a frozen backup of the history already delivered and reprocess on top of it.

Customer spreadsheet with text in numbers

Registration spreadsheets came with decimal commas, thousands separators, currency symbols, empty cells and errors such as #DIV/0!.

Convert with a safe try and turn errors and blanks into null, never zero.

Dates in four formats

The same date field came as year-month-day, month/day/year, day/month/year and spelled-out month, depending on the tenant.

Try the formats in a fixed order, record the format per tenant and flag what didn't convert.

Different calendar between engines

Weekdays numbered differently in Spark and Athena shifted the baseline to the wrong day, and movable holidays such as Easter were left out.

Use a single calendar with business days and national and movable holidays, generated once.

Acceptance test

The case is done when every item passes.

Agent package

Paste into Claude, ChatGPT or any client with the Nekt MCP connected.

Full Markdown
Data delivered to the SaaS's customers
- Tenant = a customer of the SaaS. The list of tenants and their parameters (destination layer, start date, attribution window, rate, date format) is in the tenant configuration table.
- Every question about a customer filters a single tenant. Never mix tenants in an answer for one customer.
- Each customer's layer is the permission boundary. The consolidated table is internal.
- Never expose client_secret, access_token, refresh_token, passwords or API keys.
- Attributed revenue: orders after the send and within the tenant's window (default 3 days), each order on the most recent send. Amounts in cents divided by 100.
- A tenant with truncated order timestamps (00:00:00) compares the window by date.
- A null value means no data; read the reason column. Null is not zero.
- Rows with tipo_valor = projetado are estimated from the average for the same weekday. Say they are estimated.
- Monthly report: the previous closed month against the month before it.
- Use the application tables, not the derived ones prefixed dw_ or dwt_. Filter out records with deletedAt filled in.
- If the connector generates tables with an id prefix, find the prefix by listing the tables before querying.
Per-client exceptions:
- <client>: attribution window of <n> days instead of 3.
- <client>: contracted metering only; savings in reais stay null and raise no alert.
- <client>: baseline changed on <date> due to an operational change; before and after are separate regimes.

Variations

Media and website per customer

When the connector is one account per source, each customer comes in as one more source in the same model, with the agreed canonical conversion (e.g. a sign-up event in GA4).

Raw extract for the customer

Instead of metrics, the filtered copy of the tenant's tables to their database, with the same key propagation and forbidden-columns list.

Metrics per internal department

The same design works for per-department dashboards: one layer per area, with the attainment calculation in the pipeline and not in the agent.