Integration · Marketing
How to build a campaign attribution pipeline
Marketing reports pipeline that finance does not recognise, and both numbers are defensible. Here is the model behind attribution that survives the argument, the prompts that build it, and what it takes to run in production.
Built with Tray Headless
- System Marketo
- Step Stitch to account
- Step Join spend
- Step Several models
- System Snowflake
Several models are published side by side rather than one, because the disagreement between them is the useful part.
The short answer
What is a campaign attribution pipeline?
An attribution pipeline breaks into four parts: stitching touches to the account rather than only the individual, joining spend from the ad platforms so cost per outcome is derived rather than typed, publishing several models side by side instead of one, and reconciling to a number finance recognises. The common failure is picking a single model. First touch and last touch disagree by design, and presenting one as the answer guarantees somebody with a different model calls it wrong.
What matters here
- Stitch to the account, not only the person. B2B deals involve several people and person-level attribution misses most of the influence.
- Publish more than one model. First and last touch disagree by design, and the gap between them is information instead of a problem.
- Join real spend. Cost per opportunity typed into a spreadsheet is an estimate somebody will challenge.
- Reconcile to a pipeline number finance recognises. An attribution report that cannot be tied back is a report nobody acts on.
- Keep the raw touches. Every model change is a reprocess, and you will change models.
Who this is for
You run marketing operations or revenue operations. Every quarter attribution is rebuilt in a spreadsheet, and every quarter somebody disputes it with a different model.
How it works in practice
What happens between a click and a defensible pipeline number.
- 1
Touches land raw and keep everything
Channel, campaign, timestamp, person and account where known. Raw survives every model change.
- 2
Touches are stitched to a person and an account
Anonymous to known where possible, and person to account, because B2B buying is a committee.
- 3
Spend is joined from the ad platforms
So cost per outcome is derived from real numbers, not typed into a slide.
- 4
Several models run side by side
First touch, last touch, linear and a weighted one. Each published, none presented as the answer.
- 5
The result reconciles to CRM pipeline
Total attributed pipeline ties back to a number finance already accepts.
- 6
The gap between models is reported
Because where they disagree is where the interesting conversation is.
What attribution is made of
Four parts. The third is what stops the quarterly argument.
Account-level stitching
Touch to person, person to account, including the anonymous-to-known join. B2B deals have five people and person-level attribution sees one.
Joined spend
Real cost from the ad platforms against real outcomes from the CRM. A typed estimate invites a challenge you cannot answer.
Several models
First, last, linear and weighted, published together. One model presented as truth is an invitation for somebody with another to call it wrong.
Reconciliation
Attributed pipeline tying back to CRM pipeline. Without it the report is interesting and not actionable.
The Tray Headless prompts
Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.
Once per project, run
/tray-workflows:set-workspace
to pick the workspace these build in. Point it at a sandbox first.
- 1
Set up and land the touches raw
You will change the model, and raw is what makes that cheap.
Headless skills
build-workflowUse build-workflow. The systems in play are Marketo, Salesforce and Snowflake, or whatever we run in those seats. Land every touch raw into the warehouse: channel, campaign, medium, source, content, timestamp, the person if known, the anonymous id, and the landing page. Do not transform on the way in and do not drop anything that looks unused. Every attribution model change is a reprocess of this table, and you will change models. Discarding a field now means re-instrumenting later.
- 2
Stitch to the person, then to the account
B2B buying is a committee and person-level attribution sees one member.
Headless skills
build-workflowUse build-workflow. Stitch in two stages. Anonymous to known: when somebody identifies themselves, backfill their prior anonymous touches within a sensible window. That backfill is usually where first touch actually lives. Person to account: through the CRM contact, then by email domain, then through the account matching rules we already use. Then roll touches to the account. A deal with five people touched by marketing separately is one buying decision, and attributing to individuals splits credit across a committee in a way that means nothing.
- 3
Join real spend
A typed cost per opportunity is an estimate somebody will challenge.
Pull spend from the ad platforms and join it to campaigns on a shared identifier, not on a campaign name that somebody typed differently in two places. Where a channel has no platform spend, take committed cost from the marketing budget instead of leaving it blank. An events channel with no cost attached looks impossibly efficient and discredits the whole report. Then derive cost per lead, per opportunity and per closed deal. Derived from two real numbers, never typed, so when somebody challenges it the answer is a query, not a recollection.
- 4
Publish several models, not one
Where the models disagree is the actual insight.
Headless skills
tray-gotchasUse tray-gotchas, then compute several models over the same touch set: First touch, which flatters awareness channels Last touch, which flatters closing channels Linear across all touches in the window A weighted model with the weights written down and owned by somebody Publish all of them side by side. Never present one as the answer. The gap between first and last touch for a channel is the interesting number: a channel that looks strong on first and weak on last is doing awareness work, and a report that shows only one of those will get it defunded or overfunded. Also handle: touches outside the lookback window, deals with no touches at all, which is a stitching failure instead of a sales-sourced deal, and self-reported attribution where you collect it.
Deals with zero touches are the diagnostic worth watching. A rising count almost always means stitching has broken, not that outbound suddenly got better.
- 5
Reconcile to a number finance recognises
Otherwise it is interesting and nobody acts on it.
Reconcile every run: total attributed pipeline against total CRM pipeline for the period. They will not match exactly, and the difference has to be explainable, not hand-waved. Report the unattributed share explicitly rather than hiding it. A model that quietly drops 30% of pipeline is worse than one that shows the gap. Then report by channel: spend, pipeline and closed under each model, cost per opportunity, and the first-versus-last gap. Plus deals with no touches, trended, because that is the health of the stitching rather than a finding about outbound.
- 6
Validate, then hand the weights over
Because model weights are a marketing decision, not an engineering one.
Run the per-step schema checks and the whole-workflow audit before this touches production. Then open the same workflow in Tray Build so marketing operations can adjust the lookback windows and the weighted model in the visual canvas. Those are marketing decisions and they should be arguable without a sprint.
What it connects to
Touches, spend and outcomes live in three places and have to meet in one.
Salesforce
Read opportunities and closed outcomes, and write the attributed campaign influence back.
Reads and writes
Google Analytics
Read web sessions and the anonymous-to-known join, which is where first touch usually lives.
Reads
Snowflake
Hold raw touches, spend and every model. This is where attribution actually happens.
Reads and writes
Slack
Alert when deals with no touches rise, because that is a stitching failure instead of a finding.
Writes
Same build, other stacks
The design does not change if you run something else in one of these seats. The same prompts build it against Microsoft Dynamics 365, Google BigQuery, Microsoft Teams, Power BI, HubSpot or Databricks.
Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.
Running it in production
This produces a number two departments argue about. It has to be reconstructable.
It runs on the platform, not on somebody's machine
Touch ingestion, spend joins and model runs execute on the same engine, with the raw layer retained so any model change is a reprocess.
Every number is reconstructable
Which touches, which model version, which spend snapshot. When marketing and finance disagree, that is how it gets settled in an hour.
Credentials are managed, never written into the build
Ad platform credentials expose spend across every campaign. Read-only, in your workspace, and separately rotatable.
Marketing owns the weights
Lookback windows and the weighted model open in Tray Build, because those are marketing judgements that should be arguable without engineering.
The unattributed share is published
A model that quietly drops pipeline is worse than one that shows the gap. Report it every run.
Questions people ask
Why publish several models?
Because first touch and last touch disagree by design. Presenting one as the answer guarantees somebody with a different model calls it wrong, and the gap between them is the actual insight.
Why stitch to the account?
Because B2B buying is a committee. A deal where marketing touched five people separately is one decision, and attributing to individuals splits credit in a way that tells you nothing.
Why join real spend?
Because a cost per opportunity typed into a slide is an estimate, and the first person to challenge it wins. Derived from platform spend and CRM outcomes, the answer is a query.
What does a rising count of zero-touch deals mean?
Almost always that stitching has broken, not that outbound improved. It is the best health check on the pipeline and it is worth alerting on.
Why reconcile to CRM pipeline?
Because an attribution number that cannot be tied back to one finance already accepts is interesting and unusable. Publishing the unattributed share is better than hiding it.
Vibe-coding app guides
Vibe-code an entire app with Helix
This moves the data between systems. It does not give anybody a screen to work in. Build that app in Claude Code, Codex or Cursor, then deploy and run it governed on Tray Helix. Same kind of guide, same kind of prompts.
How to build a marketing budget tracker (opens helix.tray.ai in a new tab)
Related guides
Data operations
How to build a CRM to warehouse sync
Capture history instead of current state, handle deletes and field changes, land raw then model, and prove the row counts. The Headless prompts that build it.
Marketing
How to build an ad audience sync
Build audiences from CRM state, suppress customers and open deals, hash before it leaves, and refresh often enough to matter. The prompts that build it.
Marketing
How to build a webinar and event registration sync
Get registrations in before the event, split attended from registered, route the follow-up the same day, and attribute honestly. The prompts that build it.
Last reviewed September 2026.