Goals on your own site
Conversions on a page you own — a lead form or a store — recorded without a network in the middle, and a live view answering "is it working yet".
A postback reports a sale on somebody else's server. A goal is the other half: a lead form, a checkout, an add to cart, on a page you control.
A goal is a conversion with a name and no network behind it, so everything here lands in the same conversions table the rest of the product reads. It shows up in the same reports, with the same statuses, alongside network conversions.
#Stores and lead capture
The Measurement page has three tabs, because the two things it measures work differently:
| Tab | The win | What it takes to install |
|---|---|---|
| Stores | An order, carrying its own value | A signing key and a call from your own server |
| Lead capture | A form submit, worth what you say it is | One line of JavaScript on each lander |
| Live events | — | What arrived from either in the last hour, and why anything was refused |
They are not the same instructions with a word changed. A shop signs an order carrying line items; a lead page has no order, no items and no total, and handing it a signing key would be telling somebody to build machinery for a thing they do not have.
Stores shows one shop at a time, chosen from the dropdown at the top. A store
is a real site: a name, a domain, its own site key and its own goals. Two stores
can both have a purchase and they stay separate — the store in the dropdown
decides which one you are editing. The dropdown lists every store with how it is
doing — last fired, a week's orders, or that its snippet has never been seen —
and ends with Add a store. Beside it, the chosen store's health: how many of
its goals have fired, and when the last event arrived. Under it: Goals,
Install (the snippet with its key already in it, and the signing key) and
Products (what sold, from signed orders).
Lead capture works the same way for lead pages: a set of lead goals — Lead and Registration to start with — and nothing else to set up. There is no domain or revenue rule, because a lead campaign's pages are the landers in its Funnel. The first lead page is made for you; add more with Add a lead page in the dropdown when two offers need different goals or values. A lead campaign converts on the default lead page unless you choose another on its Measurement tab, where the choice appears once there is more than one. Under the chosen page: Goals, with how many leads the buyer accepted; Where it fires; and Install.
Where it fires lists every lander the campaigns using that lead page sent clicks to in the last seven days, and whether the tag reported a page view from it. A lander with clicks and no page view has no tag on it — every lead on that page is lost, and this is the one place that says so.
Which goals a conversion is checked against is decided by the visitor's click: the campaign it came from names the store, or the lead goals, it converts on. The key in the tag only decides for a visitor no campaign sent.
#Lead capture
The win is a form submit, and it has two moments. They are different numbers.
The submission is recorded the instant the form fires, as pending, worth the fixed amount you set on the goal. That is the figure that tells you whether a landing page works.
<script>
document.querySelector('#quote-form').addEventListener('submit', function () {
utmcap.goal('lead');
});
</script>
On submit, before anything navigates away.
The acceptance comes from whoever buys the lead: a duplicate, a bad number, out of area, or good and billable. That arrives as their postback and is recorded approved, with the real payout. Give them the postback URL from the network's own page — that is the half they fill in.
Nothing is double-counted. Reports only ever take revenue from the approved one, so the pending row costs nothing while it waits.
No signing key is involved. One exists so that a shop can state an order total a browser could otherwise forge. A lead is worth what you decided it is worth, and nothing on the page can revise that.
The goals offered for a lead site:
| Key | Worth | Counted | Records as |
|---|---|---|---|
lead |
a fixed amount you set | once per visitor | pending |
registration |
nothing | once per visitor | approved |
lead is offered as the primary goal, because it is the one the campaign
should be judged on. registration is a step rather than a sale — worth
counting, worth nothing.
#A store campaign's tracking link
A store campaign has no offer and no lander. Its one path redirects to the
store chosen on its Measurement tab, with the click id on the address as
cid=. So its tracking link works like any other campaign's: paste it into
the ad, the click is recorded, the visitor lands on the store, and the tag there
reads cid and joins the visit to that click. The link is built on the tracking
domain chosen on the campaign's Details tab.
Where a platform refuses redirects, use the direct link instead — the
store's own address with the tracking parameters on it, which the tag reads as
utm_id. On Google Ads and Microsoft Ads the ad uses the Final URL, and a
tracking template is offered as well when the tracking domain is on the store's
own site.
The path takes the store's domain when the campaign is saved. After changing a store's domain, save its campaigns again.
#Store event capture
The win is an order, and four events describe the funnel in front of it. They are named after the events the rest of the industry already uses — the same words GA4 and Meta's pixel use — because somebody instrumenting a checkout is usually pasting alongside one of those, and matching names is one less thing to get wrong.
| Key | Name | Worth | Counted | Records as |
|---|---|---|---|---|
view_content |
Product viewed | nothing | once per visitor | approved |
add_to_cart |
Add to cart | nothing | once per visitor | approved |
checkout |
Checkout started | nothing | once per visitor | approved |
purchase |
Purchase | from the order | once per order | approved |
The three steps are one line of JavaScript each, at the moment they happen:
utmcap.goal('view_content');
utmcap.goal('add_to_cart');
utmcap.goal('checkout');
purchase is the one that carries money, and money is where the two lanes
diverge.
#Why an order is signed
The site key is public. It is in the page source of your own website and it has to be — the tag is served to a browser. Anyone who views source can post with it. That is fine for counting a step and not fine for stating an amount.
A value sent from a browser is recorded — refusing it would lose the conversion along with the figure — but it is marked unverified, and the screens say so. Order lines sent from a browser are dropped.
An order signed by your own server is trusted, and only a signed request may carry product lines.
#Posting an order from your server
Create the signing key on the Install view. It is shown once and never again: replace it if you lose it, and any server still signing with the old one stops being trusted immediately.
const crypto = require('crypto');
const body = JSON.stringify({
k: 'YOUR_SITE_KEY',
cid: clickId, // the _utmcap_cid cookie, carried through your checkout
goal: 'purchase',
order: order.id, // makes a refresh count once
value: order.total,
currency: 'USD',
items: order.lines.map(l => ({
sku: l.sku, name: l.title, category: l.category,
quantity: l.qty, price: l.unitPrice, total: l.lineTotal,
})),
});
await fetch('https://app.utmcap.com/api/v1/goal', {
method: 'POST',
headers: {
'content-type': 'text/plain',
'x-utmcap-signature': crypto.createHmac('sha256', SIGNING_KEY)
.update(body).digest('hex'),
},
body,
});
The same shape in any language: an HMAC-SHA256 of the exact bytes you send,
hex-encoded, in x-utmcap-signature. Sign the serialised body rather than
rebuilding it — the same object serialised again with its keys in a different
order is a different signature.
cid is the visitor's click id, and your checkout has to carry it through from
the page they landed on. The tag keeps it in the _utmcap_cid cookie; how it
reaches your server is your plumbing — a hidden field, a session value, an order
attribute.
#What the order lines buy you
The items on a signed order feed What has been selling on the Install
view: SKU, name, category, units, orders and revenue over the last 30 days.
Unsigned orders carry no lines, so that table is only ever as complete as your
signed lane.
#The two ways a goal fires
| Kind | Fires when |
|---|---|
| Event | Your page calls utmcap.goal('…') |
| Page match | The visitor reaches a path you name, such as /thank-you |
Page match needs no code, which is why it is there — but it records whatever reaching that URL means, including somebody who bookmarked it. On a checkout worth optimising, use the event.
It is a prefix match on the path, because a thank-you page almost always
carries an order id or a query string on the end, and an exact match would work
when you tested it and not in production. The longest match wins, so
/checkout/complete beats /checkout when both are defined.
A page match cannot count once per order — a URL states no order id — so the app refuses that combination when you save the goal, rather than accepting it and refusing every conversion later.
#Triggers: firing a goal without code
In Tag Manager → Triggers, a trigger fires one of your goals when something happens on the page, with no code to write. It always fires a goal, so the goal's own worth and counting rule still apply.
| Trigger | Fires when |
|---|---|
| Click | Somebody presses the element you picked |
| Form submit | A form on the page is sent |
| Comes into view | The element scrolls onto the screen |
| Time on page | The visitor has stayed a number of seconds, while still on the page |
| Leaves the page | The visitor goes, at the moment they go, if they stayed a number of seconds first |
| About to leave | The mouse heads for the top of the window: the tabs, the back button, the address bar |
#Time on page, Leaves the page and About to leave
The first two both ask for a number of seconds, so they look alike. The difference is when they fire. With 30 seconds set:
| The visitor… | Time on page | Leaves the page |
|---|---|---|
| stays 2 minutes, then closes the tab | fires at 0:30, while they read | fires at 2:00, as they close it |
| leaves after 10 seconds | does not fire | does not fire |
| stays and keeps the tab open | fires at 0:30 | fires only when they finally go |
- Time on page says they stayed. Use it for an engaged-visitor goal, or to fire a pixel for people worth retargeting.
- Leaves the page says they read it, then went. On a landing page that is the honest sign it did not convince them. It works on phones too, because it watches the tab being closed or hidden.
- About to leave is the moment somebody reaches for the address bar, the one to use for a last-chance message. It is desktop only: a phone has no mouse pointer, so it never fires there.
Give these their own goal, worth nothing and not the one the campaign is judged on. Pointed at your Lead goal, every visitor who leaves would count as a lead.
#Page event: telling your own page
A trigger records a goal. To make your page do something when it fires — open a popup, show an offer — add a Page event in Tag Manager → Pixels. Give it a name and pick the goal it follows.
When that goal fires, the tag sends the event to your page. Your own code listens for it:
window.addEventListener('utmcap:open-demo', () => openPopup());
The event also carries the goal and the click id, in event.detail.goal and
event.detail.clickId. Leave the goal empty and it fires on every page view.
Nothing is sent anywhere else, and the goal is recorded first either way. It only fires on pages that have the tag.
#What a goal is worth
| Value | Records |
|---|---|
| None | The conversion, no revenue |
| Fixed | The same amount every time |
| From the order | Whatever the caller passed in value |
Fixed suits a lead worth a known amount. From the order suits a basket, and is the only one that makes revenue reporting mean anything on a store — and the only one where the signed lane matters, because it is the only one where the caller states the number.
#Counting the same thing twice
Two dedupe modes, and the right one depends on what you are counting.
Per click gives every firing of one goal on one click the same conversion
id, so a registration reported on every page load stays one conversion.
Per order uses the order's own id, so a customer buying twice counts twice and a refreshed confirmation page counts once.
An order goal that arrives with no id is refused, not counted per click. Quietly collapsing two genuine orders into one is a worse answer than saying the call was incomplete.
#Approved or pending
Per goal, and it decides whether the conversion reaches revenue at all.
Record as pending when something confirms it later — a lead your buyer
qualifies, an order that can be cancelled. It stays out of Conversions,
Revenue, CR, EPC, ROI and Profit until something approves it, exactly
as a network conversion does. See
the figures, exactly.
Record as approved when reaching the page is the conversion.
Conversions stuck as pending are usually this setting rather than a fault: a goal recorded as pending stays pending until something approves it, and if nothing ever does — no postback, no network answer — it stays there. Check the goal's own setting first, then whether the postback that would approve it is arriving. See the four states.
#Which goal the campaign is judged on
One goal per site can be primary, and it is the one a campaign's conversion
count and ROI are read from. A shop counting view_content, add_to_cart,
checkout and purchase has four conversion counts, and only one of them is
the business.
It is never chosen for you, including by the button that adds the common goals. Quietly deciding it is how a campaign's conversion count changes without anybody having picked anything.
#Naming a goal
Lowercase letters, digits and underscores, starting with a letter, up to 64 characters. Narrow on purpose: a key ends up in a snippet somebody types by hand, in a URL, and in a column built for a small set of values — and a key differing from another by a space or a capital is a goal that silently never fires.
What you type is normalised when the goal is created — Add To Cart becomes
add_to_cart — and never when one fires. Correcting an inbound key would
make a snippet with a typo appear to work, right up until somebody fixed the
typo and the numbers moved.
#Is it working?
Live events shows what has arrived in the last hour and — the part that matters — why anything was refused. Each row says whether it came from a store or a lead page and which campaign sent the visitor, and the view filters to either, or to refusals only. It refreshes itself every few seconds while you are on it, because somebody watching that view is triggering an event by hand, and making them press a button is making them miss it.
Fire the goal on your own site, watch the row appear, and you are done. If it does not appear, the row that should have been there says what was wrong:
| Refused | What happened |
|---|---|
no_click_id |
Nothing identified the visitor — no cid, and no cookie |
bad_click_id |
The value is not a click id this tracker issues |
no_such_click |
A well-formed id that was never recorded here |
wrong_tenant |
That click belongs to another account |
unknown_goal |
No active goal with that key among the store's or lead goals the click's campaign converts on |
missing_order_id |
A per-order goal arrived with no order |
The endpoint itself answers 204 and never a reason, whatever happened. It is
called by a browser on your website, so a reason in the response would be a way
for anybody to probe which click ids and goals exist. The reason is not lost —
it is put where the person who needs it can see it, which is this view.
One visitor cannot report more than 60 conversions a minute: the limit is per site key and address, so a browser stuck in a loop cannot fill your reports. If the counter itself is unavailable the call is let through — losing a real conversion is the worse failure.
#What a goal cannot do
It cannot attribute a conversion to a click the tracker never issued. The visitor has to have arrived through UTMCAP — a tracking URL, parallel tracking, or a page carrying the tag that was recognised.
Someone who typed your URL directly, on a page with no tag, has no click id, and a goal firing for them has nothing to attach to. It is refused rather than recorded against nobody, and Live says so.
Tracking tips by email
Practical tips on tracking, attribution and ad costs, from the people who build UTMCAP.
Unsubscribe any time. Privacy policy