Guides

Multi-Tenant Visitor ID for SaaS Platforms

Architecture guide for SaaS platforms embedding visitor identification. Programmatic pixel management, data isolation, webhook routing, and scaling patterns.

Nicolas Canal Nicolas Canal · · 11 min read
Multi-Tenant Visitor ID for SaaS Platforms

You’re building a SaaS platform. Maybe it’s an AI SDR tool that needs real-time signals on who’s visiting a prospect’s website. Maybe it’s an agency dashboard managing dozens of client sites. Maybe it’s a sales intelligence product that wants to layer visitor identification into its feature set.

Whatever it is, your clients each want to know who’s visiting their website. That means separate pixels, separate data, separate billing. No cross-contamination. No manual setup for each new account.

Welcome to multi-tenant visitor identification.

This guide walks you through the architecture — from programmatic pixel creation to webhook routing to scaling patterns — using Leadpipe’s API as the identity layer. If you’ve already read the 10-minute quickstart, think of this as the production-grade sequel.

We’re not going to hand-wave through the hard parts. You’ll get working code, ASCII architecture diagrams, and real decisions you’ll need to make at each scale tier.


Table of Contents


The Multi-Tenant Challenge

If you’re embedding visitor identification into a platform, you have six problems to solve simultaneously:

  1. Pixel isolation — Each client gets their own tracking pixel. Client A’s pixel tracks Client A’s site. Period.
  2. Data isolation — Visitor data stays separate per client. No accidental cross-contamination where Client B sees Client A’s leads.
  3. Webhook routing — When a visitor is identified, the data needs to land in the right place for the right client.
  4. Usage tracking — Credits and identifications need to be tracked per client so you can bill accurately.
  5. Programmatic onboarding — New clients get set up via API. No logging into a dashboard. No manual pixel creation.
  6. Programmatic offboarding — When a client churns, their pixel stops, their data stays, and your platform keeps running.

Building an identity graph from scratch to solve these problems is a multi-year, multi-million dollar project. The build-vs-buy math doesn’t work for 99% of platforms. What works: using an API that was designed for exactly this pattern.


Architecture Overview

Here’s the full picture of how a multi-tenant setup works with a single Leadpipe API key managing multiple client pixels:

┌─────────────────────────────────────────────────────────┐
│              YOUR SAAS PLATFORM                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Client A        Client B        Client C               │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐             │
│  │ Pixel A │    │ Pixel B │    │ Pixel C │              │
│  └────┬────┘    └────┬────┘    └────┬────┘             │
│       │              │              │                    │
│       ▼              ▼              ▼                    │
│  ┌─────────────────────────────────────────┐            │
│  │       Leadpipe API (one API key)        │            │
│  │   POST /v1/data/pixels (create)         │            │
│  │   GET /v1/data?domain=X (query)         │            │
│  │   Webhooks per pixel                    │            │
│  └─────────────────────────────────────────┘            │
│       │              │              │                    │
│       ▼              ▼              ▼                    │
│  Your webhook router → per-client data stores            │
└─────────────────────────────────────────────────────────┘

One API key. Multiple pixels. Isolated data per client. Your platform sits between Leadpipe and your end users. They never see Leadpipe. They see your product. This is the same OEM pattern that platforms like AISDR use to process 250,000+ identifications per month.

The key insight: Leadpipe’s domain filter on the data endpoint gives you per-client isolation without building any multi-tenant data layer yourself. Each pixel is scoped to one domain, and you query by domain. It’s multi-tenancy for free.


Step 1: Programmatic Pixel Creation

When a new client signs up for your platform, you create their pixel via a single API call. No dashboard. No manual steps.

async function onboardClient(clientId, clientDomain) {
  const response = await fetch('https://api.aws53.cloud/v1/data/pixels', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.LEADPIPE_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      domain: clientDomain,
      name: `Client ${clientId}`
    })
  });

  const pixel = await response.json();

  // Response:
  // {
  //   id: "uuid-1234-5678",
  //   domain: "clientsite.com",
  //   name: "Client abc123",
  //   status: "active",
  //   code: "<script>...</script>",
  //   createdAt: "2026-04-01T12:00:00Z"
  // }

  // Store pixel.id and pixel.code in your database
  await db.clients.update(clientId, {
    pixelId: pixel.id,
    pixelCode: pixel.code,
    domain: pixel.domain
  });

  return pixel;
}

The response gives you everything you need:

FieldWhat It’s For
idUUID to manage the pixel later (pause, update, query)
domainThe domain this pixel tracks
nameYour label — use it for internal tracking
statusactive or paused
codeThe JavaScript snippet to install on the client’s site
createdAtTimestamp for your audit logs

The code field is the pixel itself. Display it in your onboarding flow, email it to the client, or inject it programmatically if you control their site. This is the same two-minute install described in our visitor identification setup guide, but triggered by your backend instead of a human.

Naming convention tip: Use a consistent naming pattern from day one. Something like {clientId}-{domain} or {companyName} makes it much easier to audit your pixel fleet when you’re managing 50 or 200 of them.


Step 2: Data Isolation by Domain

Each pixel is scoped to one domain. When you query visitor data, you filter by domain — and you only get results for that specific client’s site. Zero risk of cross-contamination.

async function getClientVisitors(clientDomain, timeframe = '7d') {
  const response = await fetch(
    `https://api.aws53.cloud/v1/data?domain=${encodeURIComponent(clientDomain)}&timeframe=${timeframe}&page=1`,
    {
      headers: { 'X-API-Key': process.env.LEADPIPE_KEY }
    }
  );

  return response.json();
}

// Usage in your dashboard endpoint
app.get('/api/clients/:clientId/visitors', async (req, res) => {
  const client = await db.clients.findById(req.params.clientId);
  const visitors = await getClientVisitors(client.domain, req.query.timeframe);
  res.json(visitors);
});

This is where the architecture gets elegant. You don’t need to build per-client database partitions or row-level security for visitor data. Leadpipe stores it, isolated by domain. You query it on demand.

For clients who want historical data, just change the timeframe parameter. For paginated results on high-traffic sites, increment the page parameter.

What data comes back? Full names, business emails, personal emails, phone numbers, LinkedIn profile URLs, job titles, company information, pages viewed, visit durations, and return visit tracking. Person-level, deterministic matching — not company-only IP lookup guesses.


Step 3: Webhook Routing

Querying the API works for dashboards and reports, but most platforms need real-time data delivery. That’s where webhooks come in. Leadpipe fires a POST request to your endpoint every time a visitor is identified.

The question: how do you route webhook data to the right client?

All pixels send webhooks to a single endpoint on your platform. Your router inspects the incoming payload and routes it to the correct client.

const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhook/leadpipe', async (req, res) => {
  const { domain, email, first_name, last_name, company_name,
          job_title, linkedin_url, page_url } = req.body;

  // Look up which client owns this domain
  const client = await db.clients.findOne({ domain });

  if (!client) {
    console.warn(`Webhook received for unknown domain: ${domain}`);
    return res.status(200).send('OK'); // Always 200 to avoid retries
  }

  // Store visitor data scoped to this client
  await db.visitors.insert({
    clientId: client.id,
    email,
    name: `${first_name} ${last_name}`,
    company: company_name,
    title: job_title,
    linkedin: linkedin_url,
    pageUrl: page_url,
    receivedAt: new Date()
  });

  // Track usage for billing
  await db.usage.increment(client.id, 'identifications');

  // Trigger client-specific workflows
  await notifyClient(client.id, req.body);

  res.status(200).send('OK');
});

Why this pattern wins for most platforms: One webhook URL to configure across all pixels. Simple to monitor, log, and debug. Your router handles the fan-out.

Pattern B: Per-Client Webhook URLs

Each pixel gets a unique webhook URL that includes the client identifier:

https://yoursaas.com/webhook/leadpipe/client-abc123
https://yoursaas.com/webhook/leadpipe/client-def456
https://yoursaas.com/webhook/leadpipe/client-ghi789
app.post('/webhook/leadpipe/:clientId', async (req, res) => {
  const { clientId } = req.params;
  await processVisitorForClient(clientId, req.body);
  res.status(200).send('OK');
});

When to use Pattern B: If you need to configure different webhook triggers per client (some want First Match only, others want Every Update), per-client URLs make that management cleaner.

Webhook Trigger Modes

Leadpipe supports two trigger modes, configurable per pixel:

  • First Match — Fires once per visitor, the first time they’re identified. Best for lead capture and CRM record creation.
  • Every Update — Fires whenever new data is appended to a known visitor. Best for behavioral tracking, return visit alerts, and engagement scoring.

Many production integrations use both: First Match to create the lead, Every Update to track ongoing engagement. If you’re building AI agent integrations, Every Update gives your agents the freshest context.


Step 4: Pixel Management at Scale

Once you’re running 10, 50, or 500 pixels, you need operational control. The API gives you three management levers.

Pause and Resume Pixels

When a client churns, downgrades, or pauses their subscription, pause their pixel instead of deleting it. This stops identification (and credit consumption) while preserving historical data.

// Pause a client's pixel
async function pauseClient(pixelId) {
  await fetch(`https://api.aws53.cloud/v1/data/pixels/${pixelId}`, {
    method: 'PATCH',
    headers: {
      'X-API-Key': process.env.LEADPIPE_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ status: 'paused' })
  });
}

// Resume when they reactivate
async function resumeClient(pixelId) {
  await fetch(`https://api.aws53.cloud/v1/data/pixels/${pixelId}`, {
    method: 'PATCH',
    headers: {
      'X-API-Key': process.env.LEADPIPE_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ status: 'active' })
  });
}

Excluded Paths

Clients don’t want to identify visitors on their login page, admin panel, or internal tools. Set excluded paths per pixel to skip those URLs.

// Exclude admin and login pages from tracking
await fetch(`https://api.aws53.cloud/v1/data/pixels/${pixelId}`, {
  method: 'PATCH',
  headers: {
    'X-API-Key': process.env.LEADPIPE_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    excludedPaths: ['/admin', '/login', '/internal', '/dashboard']
  })
});

Each path must start with /. Maximum 50 excluded paths per pixel. To clear exclusions, send excludedPaths: null.

Monitor Your Pixel Fleet

List all pixels across your platform to build operational dashboards:

async function getPixelFleet() {
  const response = await fetch('https://api.aws53.cloud/v1/data/pixels', {
    headers: { 'X-API-Key': process.env.LEADPIPE_KEY }
  });

  const pixels = await response.json();

  // Returns array of:
  // {
  //   id, domain, name, status,
  //   pausedReason, createdAt
  // }

  const active = pixels.filter(p => p.status === 'active');
  const paused = pixels.filter(p => p.status === 'paused');

  console.log(`Fleet: ${active.length} active, ${paused.length} paused`);
  return pixels;
}

Build this into a health check that runs on a cron. Alert if a pixel is unexpectedly paused. Track your fleet size over time to forecast credit usage.


Step 5: Usage Tracking and Billing

Your Leadpipe account has a single credit pool shared across all pixels. You need to track per-client consumption so you can bill accurately.

Account-Level Health Check

The account endpoint gives you a top-level view of your credit pool:

async function checkAccountHealth() {
  const response = await fetch('https://api.aws53.cloud/v1/data/account', {
    headers: { 'X-API-Key': process.env.LEADPIPE_KEY }
  });

  const account = await response.json();

  // Returns:
  // {
  //   organization: { name, status },
  //   credits: {
  //     used: 12500,
  //     limit: 20000,
  //     remaining: 7500,
  //     percentUsed: 62.5,
  //     resetsAt: "2026-05-01T00:00:00Z"
  //   },
  //   pixels: { total: 47, active: 42, paused: 5 },
  //   intent: {
  //     audienceSlots, audienceSlotsUsed,
  //     audienceSlotsAvailable, status
  //   },
  //   healthy: true
  // }

  if (account.credits.percentUsed > 80) {
    alertOps('Credit usage above 80% -- consider upgrading');
  }

  return account;
}

Per-Client Usage Tracking

The account endpoint tracks aggregate usage, not per-client breakdowns. To build per-client billing, count webhook deliveries per domain in your database:

// In your webhook handler (from Step 3)
app.post('/webhook/leadpipe', async (req, res) => {
  const { domain } = req.body;
  const client = await db.clients.findOne({ domain });

  // Increment per-client counter
  await db.usage.increment({
    clientId: client.id,
    month: getCurrentMonth(),
    metric: 'identifications'
  });

  // ... rest of your processing
  res.status(200).send('OK');
});

// Monthly billing query
async function getClientUsage(clientId, month) {
  const usage = await db.usage.findOne({ clientId, month });
  return usage?.identifications || 0;
}

This gives you the data to build tiered pricing, usage-based billing, or flat-fee plans for your clients — whatever model works for your platform.


Step 6: Client Offboarding

When a client leaves your platform, the offboarding sequence is straightforward:

async function offboardClient(clientId) {
  const client = await db.clients.findById(clientId);

  // 1. Pause the pixel (don't delete -- preserves history)
  await pauseClient(client.pixelId);

  // 2. Stop processing webhooks for this client
  await db.clients.update(clientId, { status: 'inactive' });

  // 3. Optionally export their data before cleanup
  const visitors = await getClientVisitors(client.domain, '90d');
  await exportToCSV(visitors, `${clientId}-final-export.csv`);

  console.log(`Client ${clientId} offboarded. Pixel ${client.pixelId} paused.`);
}

Why pause instead of delete? Three reasons:

  1. Data retention — If the client comes back, their historical data is still there.
  2. Compliance audit trail — You can prove what data was collected and when.
  3. No orphaned records — Your internal references to the pixel ID remain valid.

Paused pixels don’t consume credits. They just sit quietly until you reactivate them.


Scaling Patterns

The right architecture depends on how many clients you’re managing. Here’s what works at each tier:

ScaleClientsArchitectureKey Considerations
Starter1-10Central webhook, single DB tableSimple. No optimization needed. Focus on getting the integration right.
Growth10-100Central webhook, per-client DB partitionsAdd a queue (Redis or SQS) between webhook receipt and processing. Prevents slow processing from backing up webhook responses.
Scale100-1,000Central webhook + queue + worker poolBatch visitor queries. Cache account health checks (poll every 5 min, not per-request). Consider read replicas for your visitor data store.
Enterprise1,000+Multiple API keys, dedicated webhook infrastructureContact Leadpipe for volume pricing and dedicated support. Shard by API key for independent credit pools.

The Queue Pattern (Growth Tier and Above)

Once you’re past ~10 clients, you don’t want webhook processing to block your HTTP response. Here’s the pattern:

Webhook → Your Endpoint → Queue (SQS/Redis) → Worker Pool → Database
             (200 OK                              ↓
              immediately)                   Client-specific
                                             processing
// Webhook handler: acknowledge immediately, process async
app.post('/webhook/leadpipe', async (req, res) => {
  await queue.push('visitor-identified', req.body);
  res.status(200).send('OK'); // Return fast
});

// Worker: process at your own pace
queue.process('visitor-identified', async (job) => {
  const { domain } = job.data;
  const client = await db.clients.findOne({ domain });
  await processVisitorForClient(client.id, job.data);
  await db.usage.increment(client.id, 'identifications');
});

This decouples webhook receipt from processing. Your endpoint always returns 200 in milliseconds. Your workers handle the heavy lifting — database writes, notifications, AI agent feeds, CRM syncs — at whatever pace they can sustain.


Security Considerations

Multi-tenant systems require extra care. Here’s the checklist:

API key management:

  • Store your Leadpipe API key in environment variables. Never in client-side code. Never in your Git repo.
  • Your clients should never see the key. They interact with your platform — your platform talks to Leadpipe.

Webhook endpoint security:

  • Rate limit your webhook endpoint to prevent abuse.
  • Validate the incoming payload structure before processing.
  • Always return 200 OK, even for payloads you can’t process. Non-200 responses trigger retries, which can amplify issues.

Data access control:

  • Never expose raw Leadpipe API responses to clients. Transform the data into your product’s format.
  • Ensure your per-client queries are properly scoped. A bug in domain filtering could leak data between clients.
  • If you’re white-labeling the data, strip any Leadpipe references before displaying it to end users.

Compliance:

  • Leadpipe is CCPA compliant and provides company-level only data for EU visitors (GDPR).
  • Your platform inherits these protections, but you’re responsible for how you store and use the data downstream.
  • Implement data retention policies per client. Don’t keep visitor data forever unless your clients need it.
  • If your clients operate in regulated industries (healthcare, finance), layer additional access controls on top.

Logging and audit:

  • Log every webhook received, including timestamp, domain, and processing status. This is your audit trail.
  • Track which internal users accessed which client’s data and when.
  • Set up alerts for anomalies — a sudden spike in webhook volume for one domain could indicate a tracking issue or a traffic bot attack.

A note on trust: Your clients are trusting you with their website visitor data. Treat that seriously. Encrypt at rest, encrypt in transit, audit access logs, and have an incident response plan. Multi-tenancy means one misconfigured query or one leaked credential can affect every client on your platform. The good news: because Leadpipe handles the identity resolution infrastructure, your attack surface is limited to your own application layer. You don’t need to secure an identity graph — just your webhook receiver, database, and client-facing API.


Cost Model and Margins

Let’s talk numbers. This is where multi-tenant visitor ID gets financially interesting.

Leadpipe Agency plan: $1,279/mo for 20,000 identifications. That’s $0.064 per identified visitor. Unlimited pixels — no per-client surcharge.

Here’s what the math looks like at different scales:

ScenarioClientsIDs/Client/MoTotal IDsYour CostCharge/ClientRevenueMargin
Conservative105005,000$1,279$500$5,00074%
Growth201,00020,000$1,279$750$15,00091%
Aggressive151,00015,000$1,279$2,000$30,00096%

Compare that to traditional SaaS margins of 60-70%. Visitor identification as a platform feature is an extremely high-margin add-on.

The key insight: you’re not reselling Leadpipe. You’re embedding identification into a larger product. Clients pay for what your platform does with the data — AI-powered outreach, lead scoring, campaign optimization, whatever your core value prop is. The identification is the data layer beneath it.

Compare this to the alternative: licensing identity data from LiveRamp ($300K+/year), building pixel infrastructure, hiring a data engineering team, and maintaining compliance across jurisdictions. That’s a minimum $500K annual commitment before you identify a single visitor. With the API approach, you’re live in a day and profitable with your first client.

For a deeper look at how this compares to building in-house or buying enterprise contracts, see the build vs. buy analysis. For detailed pricing across all tiers, check the visitor identification pricing breakdown.

Try Leadpipe free with 500 leads ->


FAQ

Can I use multiple API keys to separate client groups?

Yes. At the Enterprise tier (1,000+ clients), some platforms use multiple API keys to create independent credit pools — for example, one key per geographic region or per client tier. Each key gets its own credit allocation and pixel fleet. Talk to Leadpipe’s team about volume structuring.

What happens if I hit my credit limit mid-month?

Pixels continue to collect behavioral data (page views, sessions), but new visitor identifications pause until credits reset. The credits.resetsAt field from the account health endpoint tells you exactly when the reset happens. Build alerts at 80% usage so you’re never caught off guard.

How do I handle clients with multiple domains?

Create one pixel per domain. A single client might have marketing-site.com, blog.clientname.com, and app.clientname.com. Three pixels, all linked to the same client in your database. Query each domain separately or aggregate the data in your application layer.

Can I use this with the Intent API too?

Absolutely. The Leadpipe Intent API lets you build audiences around 20,000+ topics and monitor which of your clients’ prospects are actively researching relevant keywords across the web. Combine visitor identification (who’s on the site right now) with intent data (who’s researching your topic right now) for a complete picture. The account health endpoint shows your audienceSlots allocation alongside credit usage.


Start Building

You’ve got the architecture. You’ve got the code. The fastest way to validate this for your platform is to create a free account, mint your first pixel via the API, and see visitor data flowing in real time.

500 free identifications. No credit card. Full API access from day one.

Start your free Leadpipe account and build your first multi-tenant integration ->