---
title: "Marketing Leadgen"
canonical: "https://www.owox.com/data-models/marketing-leadgen"
updated: "2026-09-26"
---

# Marketing Leadgen Data Model

8 data marts70 fields[Vlad Flaks](https://github.com/vladflaks)[Rus Obolonsky](https://github.com/Obolrus)

Campaigns anchor a B2B demand-generation business, tying ad spend, web traffic and attribution to leads that move through pipeline stages to a closed deal, with accounts carrying the firmographics ABM depends on.

## Overview

A B2B demand-generation and revenue-operations business modeled end to end — from ad spend and marketing campaigns, through web traffic and multi-touch attribution, to leads, sales opportunities and the pipeline stages that carry them to close. Campaigns anchor everything, paid and non-paid alike: every dollar of ad spend, every web session and every touch on the path to conversion ties back to a campaign, so channel performance can be traced cleanly from a click to a closed-won deal. Account carries the firmographics and the ABM target-list flag, since real B2B deals are won or lost at the account level, not the individual lead level.

## Example Questions

*   Which channels and `campaigns` generate the most pipeline and revenue once `ad spend`, attribution credit and win rates are accounted for?
*   Where does the funnel leak most — awareness to `lead`, lead to MQL/SQL, or `opportunity` to close — and does that differ by channel?
*   Do target (ABM) `accounts` convert, close and win at a higher rate than the rest of the book?

[Explore on canvas →](https://model.owox.com/?okf=https://github.com/OWOX/models/tree/main/bundles/marketing-leadgen)

## Account

One row per target company, the firmographic backbone the rest of the model hangs off. Every account carries an industry, an employee-count band and a region, plus a flag marking whether it sits on the account-based marketing (ABM) target list. Because real B2B deals belong to accounts rather than to whichever individual filled out a form, this mart is the join point that lets leads, opportunities and touchpoints be rolled up to "who is this company, and how big an opportunity is it."

### Fields

| Column | Type | Alias | Description |
| --- | --- | --- | --- |
| `account_id` | STRING | Account ID | PK. Unique identifier for the target company. |
| `name` | STRING | Name | Company name. |
| `industry` | STRING | Industry | Industry the company operates in. |
| `employee_band` | STRING | Employee Band | Company-size bucket — the mid-market segmentation axis. Vocabulary: `1-50` / `51-200` / `201-1000` / `1000+`. |
| `region` | STRING | Region | Geographic region of the company. |
| `is_target_account` | BOOLEAN | Is Target Account | Whether the company is on the ABM target list. ~10–20% of accounts, skewed toward larger `employee_band`; target accounts show higher engagement, pipeline and win rate. |

## Ad Spend

Daily cost, impressions and clicks for every paid campaign, broken out by ad group. This is the cost side of the funnel — the numbers a demand-gen team reconciles against Google Ads, LinkedIn Ads and other platform reports every week to know what a click, a lead and ultimately a closed deal actually costs.

### Fields

| Column | Type | Alias | Description |
| --- | --- | --- | --- |
| `spend_id` | STRING | Spend ID | PK. Unique identifier for each spend record. |
| `spend_date` | DATE | Spend Date | Day the spend was incurred. Within the campaign's `[start_date, end_date]` flight. |
| `campaign_id` | STRING | Campaign ID | Campaign this spend belongs to. FK to [Campaign](#mart-campaign) |
| `channel` | STRING | Channel | Marketing channel where the cost was spent. Denormalized copy of the campaign's `channel`. |
| `ad_group` | STRING | Ad Group | Ad group or ad set within the campaign. |
| `impressions` | INTEGER | Impressions | Number of times ads were shown. |
| `clicks` | INTEGER | Clicks | Number of clicks the ads received. Always `≤ impressions`; CTR by channel. |
| `cost` | NUMERIC | Cost | Money spent on this ad group for the day (USD). Derived from clicks × per-channel CPC. |

### Relationships

| Related data mart | On | Cardinality | Meaning |
| --- | --- | --- | --- |
| [Campaign](#mart-campaign) | `campaign_id = campaign_id` | N:1 | The campaign this spend was bought for. |

## Campaign

Reference of every marketing initiative run — paid and non-paid alike, from search and social ads to email nurtures, webinars and content syndication — each carrying its channel, its objective and the UTM tags that tie it back to ad-platform and web-analytics data. This is the conformed campaign dimension of the model: every channel value that shows up on ad spend, web sessions, touchpoints or opportunities agrees with what is declared here, so a channel-level report never splits into phantom categories.

### Fields

| Column | Type | Alias | Description |
| --- | --- | --- | --- |
| `campaign_id` | STRING | Campaign ID | PK. Unique identifier for each campaign. |
| `campaign_name` | STRING | Campaign Name | Human-readable name of the campaign. |
| `channel` | STRING | Channel | Marketing channel. Controlled vocabulary: `paid_search`, `paid_social`, `display`, `organic_search`, `email`, `webinar`, `content_syndication`, `direct`, `referral`. |
| `objective` | STRING | Objective | Primary goal. One of: `awareness`, `lead_generation`, `conversion`, `retargeting`. |
| `utm_source` | STRING | UTM Source | UTM source tag identifying where the traffic originates. |
| `utm_medium` | STRING | UTM Medium | UTM medium tag describing the type of traffic (e.g. cpc, email). |
| `utm_campaign` | STRING | UTM Campaign | UTM campaign tag — the key ad-platform ↔ web-analytics join key. |
| `start_date` | DATE | Start Date | Date the campaign went live. |
| `end_date` | DATE | End Date | Date the campaign flight ended (nullable for always-on). Spend/touches fall within `[start_date, end_date]`. |

## Lead

One row per lead or contact captured by marketing, carrying where they came from, their fit-and-engagement score, the dated milestones marking when they became marketing-qualified (MQL) and sales-qualified (SQL), and firmographics that mirror the account they belong to. Because the qualification milestones are dated rather than just a current-state label, leads can be grouped into cohorts — "leads created in March" — and tracked through the funnel over time, not just counted at a single point.

### Fields

| Column | Type | Alias | Description |
| --- | --- | --- | --- |
| `lead_id` | STRING | Lead ID | PK. Unique identifier for each lead or contact. |
| `account_id` | STRING | Account ID | Company the lead belongs to; null for a lead not yet matched to an account. FK to [Account](#mart-account) |
| `created_at` | TIMESTAMP | Created At | When the lead first entered the system (coincides with the `is_lead_create` touchpoint). |
| `source_channel` | STRING | Source Channel | Channel that first brought in the lead. Equals the channel of the lead's `is_lead_create` touchpoint. Same controlled vocabulary as [Campaign](#mart-campaign)`.channel`. |
| `lead_score` | INTEGER | Lead Score | Fit + engagement score for MQL gating. |
| `became_mql_at` | TIMESTAMP | Became Mql At | When the lead reached marketing-qualified status; null if never MQL. `created_at ≤ became_mql_at`. |
| `became_sql_at` | TIMESTAMP | Became Sql At | When the lead reached sales-qualified status; null if never SQL. `became_mql_at ≤ became_sql_at`. |
| `employee_band` | STRING | Employee Band | Company-size bucket. Same vocabulary as [Account](#mart-account)`.employee_band`: `1-50` / `51-200` / `201-1000` / `1000+`. For matched leads, agrees with the account. |
| `industry` | STRING | Industry | Industry the lead's company operates in. For matched leads, agrees with the account. |
| `country` | STRING | Country | Country where the lead is located; consistent with the account's `region`. |
| `lifecycle_stage` | STRING | Lifecycle Stage | `subscriber` / `lead` / `MQL` / `SQL` / `opportunity` / `customer`. Consistent with the `became_*_at` timestamps and with the existence of an [Opportunities](#mart-opportunities) row. |

### Relationships

| Related data mart | On | Cardinality | Meaning |
| --- | --- | --- | --- |
| [Account](#mart-account) | `account_id = account_id` | N:1 | The company the lead works for. |

## Opportunities

One row per sales opportunity — the deal object, keyed to the account rather than a single lead, since real B2B buying committees involve multiple people. Each row carries the pipeline stage, the deal size (ACV), the sourcing lead and campaign, and the win/loss outcome with close date and sales-cycle length. This is where marketing-sourced pipeline turns into — or fails to turn into — closed revenue.

### Fields

| Column | Type | Alias | Description |
| --- | --- | --- | --- |
| `opportunity_id` | STRING | Opportunity ID | PK. Unique identifier for each sales opportunity. |
| `account_id` | STRING | Account ID | Account the deal belongs to — the primary spine. FK to [Account](#mart-account) |
| `lead_id` | STRING | Lead ID | Sourcing/converting lead the opportunity originated from; nullable. FK to [Lead](#mart-lead) |
| `primary_campaign_id` | STRING | Primary Campaign ID | Primary Campaign Source (single-touch sourcing); nullable. Equals the campaign of the sourcing lead's `is_lead_create` touchpoint. FK to [Campaign](#mart-campaign) |
| `created_at` | TIMESTAMP | Created At | When the opportunity was created. |
| `stage` | STRING | Stage | Current pipeline stage. One of: `discovery`, `demo`, `proposal`, `negotiation`, `closed_won`, `closed_lost`. |
| `amount` | NUMERIC | Amount | ACV / deal size (USD). Correlates with the account's `employee_band`. |
| `close_date` | DATE | Close Date | Date the opportunity was won or lost; null while open. |
| `is_won` | BOOLEAN | Is Won | True iff `stage = closed_won` (then `close_date` is set); false with a `close_date` means `closed_lost`. |
| `sales_cycle_days` | INTEGER | Sales Cycle Days | `DATE_DIFF(close_date, created_at, DAY)` for closed deals; null while open. |
| `owner` | STRING | Owner | Sales rep who owns the opportunity (drawn from a small stable roster). |

### Relationships

| Related data mart | On | Cardinality | Meaning |
| --- | --- | --- | --- |
| [Account](#mart-account) | `account_id = account_id` | N:1 | The company the deal is with. |
| [Campaign](#mart-campaign) | `primary_campaign_id = campaign_id` | N:1 | The campaign credited with sourcing the deal. |
| [Lead](#mart-lead) | `lead_id = lead_id` | N:1 | The lead the deal grew out of. |

## Stage Transitions

One row per pipeline stage change for a sales opportunity, tracing each deal's path through discovery, demo, proposal and negotiation on the way to a win or a loss. The time spent in each stage before moving to the next is the raw material for bottleneck analysis — where deals get stuck, and for how long.

### Fields

| Column | Type | Alias | Description |
| --- | --- | --- | --- |
| `transition_id` | STRING | Transition ID | PK. Unique identifier for the stage change. |
| `opportunity_id` | STRING | Opportunity ID | Opportunity that moved stage. FK to [Opportunities](#mart-opportunities) |
| `from_stage` | STRING | From Stage | Stage the opportunity left. |
| `to_stage` | STRING | To Stage | Stage the opportunity entered. Contiguous chain: `to_stage` of row n = `from_stage` of row n+1; the last `to_stage` equals `Opportunities.stage`. |
| `transitioned_at` | TIMESTAMP | Transitioned At | When the stage change happened. Within `[Opportunities.created_at, close_date]`. |
| `days_in_from_stage` | INTEGER | Days In From Stage | Days spent in the previous stage — the velocity bottleneck driver. Sums (per opportunity) to the sales cycle for closed deals. |

### Relationships

| Related data mart | On | Cardinality | Meaning |
| --- | --- | --- | --- |
| [Opportunities](#mart-opportunities) | `opportunity_id = opportunity_id` | N:1 | The deal that moved stage. |

## Touchpoints

One row per marketing touch a lead has on the way to becoming a customer, credited under a W-shaped multi-touch attribution model that splits credit 30% to the first touch, 30% to lead creation, 30% to opportunity creation and 10% across everything in between. This is the mart that answers "which channels and campaigns actually deserve credit," rather than crediting only the first click or the last one.

### Fields

| Column | Type | Alias | Description |
| --- | --- | --- | --- |
| `touchpoint_id` | STRING | Touchpoint ID | PK. Unique identifier for each marketing touch. |
| `lead_id` | STRING | Lead ID | Lead that this touch belongs to. FK to [Lead](#mart-lead) |
| `campaign_id` | STRING | Campaign ID | Campaign associated with this touch. FK to [Campaign](#mart-campaign) |
| `occurred_at` | TIMESTAMP | Occurred At | When the touch happened. |
| `channel` | STRING | Channel | Channel where the touch occurred. Equals the campaign's `channel`; same controlled vocabulary as [Campaign](#mart-campaign)`.channel`. |
| `touch_type` | STRING | Touch Type | Kind of interaction. One of: `ad_click`, `form_fill`, `email_open`, `email_click`, `webinar_attend`, `content_download`, `demo_request`. Consistent with `channel` (e.g. no `email_open` on `paid_search`). |
| `touch_credit` | FLOAT | Touch Credit | W-shaped credit for this touch. Sums to exactly 1.0 per lead: 30% first touch, 30% lead creation, 30% opportunity creation, 10% across middle touches. |
| `is_first_touch` | BOOLEAN | Is First Touch | True on the lead's earliest touch (exactly one per lead). W-shaped anchor. |
| `is_lead_create` | BOOLEAN | Is Lead Create | True on the touch that created the lead (exactly one per lead; coincides with `Lead.created_at`). W-shaped anchor. |
| `is_opp_create` | BOOLEAN | Is Opp Create | True on the touch that created the opportunity (at most one per lead; only for leads with an opportunity; coincides with `Opportunities.created_at`). W-shaped anchor. |

### Relationships

| Related data mart | On | Cardinality | Meaning |
| --- | --- | --- | --- |
| [Campaign](#mart-campaign) | `campaign_id = campaign_id` | N:1 | The campaign that produced this touch. |
| [Lead](#mart-lead) | `lead_id = lead_id` | N:1 | The lead who was touched. |

## Web Sessions

One row per web session, the very top of the funnel, before most visitors are ever identified as a lead. Each session records the traffic source and medium, the landing page, whether it converted into a form submission, and — for the small share of visitors identified through identity stitching — the lead that session belongs to. Because the overwhelming majority of B2B web traffic never converts or gets identified, most sessions carry no lead at all; the ones that do show exactly where anonymous traffic turns into a known contact.

### Fields

| Column | Type | Alias | Description |
| --- | --- | --- | --- |
| `session_id` | STRING | Session ID | PK. Unique identifier for the web session. |
| `lead_id` | STRING | Lead ID | Known lead after identity stitching; null for never-identified visitors (the overwhelming majority). FK to [Lead](#mart-lead) |
| `started_at` | TIMESTAMP | Started At | When the session began. |
| `campaign_id` | STRING | Campaign ID | Campaign that drove the session; null for organic/direct. FK to [Campaign](#mart-campaign) |
| `source` | STRING | Source | Traffic source that referred the session; consistent with the driving campaign's `utm_source`. |
| `medium` | STRING | Medium | Marketing medium (e.g. organic, cpc, email); consistent with the driving campaign's `utm_medium`. |
| `landing_page` | STRING | Landing Page | First page viewed in the session. |
| `form_submits` | INTEGER | Form Submits | Number of forms submitted during the session. `≥ 1` when `is_conversion` is true. |
| `is_conversion` | BOOLEAN | Is Conversion | Whether the session produced a lead or demo request. Session→lead conversion ~1–3% overall, lowest on paid social. |

### Relationships

| Related data mart | On | Cardinality | Meaning |
| --- | --- | --- | --- |
| [Campaign](#mart-campaign) | `campaign_id = campaign_id` | N:1 | The campaign that drove the visit. |
| [Lead](#mart-lead) | `lead_id = lead_id` | N:1 | The lead this visit was later tied to. |

## Apply to your project

1.  1

    ### Install the Import Model plugin

    One plugin, installed once, in your own OWOX workspace.

    [Get the plugin →](https://github.com/OWOX/import-model)

2.  2

    ### Import this model

    Point it at this bundle and it creates every data mart above, joins and all.

    [Open the model →](https://model.owox.com/?okf=https://github.com/OWOX/models/tree/main/bundles/marketing-leadgen)

3.  3

    ### Plug in your data and destinations

    Connect your own sources and send the results where your team already works.

    [Browse connectors →](/connectors)

**4\. Optional — customize as you wish.** Rename a column, drop a mart, add your own: once it is imported it is yours, and nothing here syncs back.

## References

Pages this page links to, on this site and on docs.owox.com. Where the page has a Markdown twin, its address follows the link.

- [Browse connectors →](https://www.owox.com/connectors) — /connectors.md
