Track plain HTML forms
HTML Forms is not a connection. There is nothing to authorise and no token to paste — you install code on your own site instead, and this article covers the two ways of doing that. If your form is built in HubSpot, Marketo or a CMS form plugin, read that tool’s own article rather than this one.
Before you start
Permissions required
You need someone who can edit your site’s HTML template. For the second route you also need whoever owns the code that receives the form post. Copying your API Key needs no special role; rotating it needs the workspace admin role, and MarqIQ answers a non-admin with "Only workspace admins can rotate the API key."
Plan required
Both routes work on every plan, a Free Trial included. Your plan sets two other things: how many fills a month MarqIQ stores, and which screens read them. Home shows your fills on every plan; the Attribution & Journeys screens need Pro or above.
Two routes, and they are not the same job
The first route gets your form fills into MarqIQ’s own reports. The second route gets the campaign values into the form post your own code receives. Read the comparison below before you copy anything, because the two use different input names and mixing them leaves fields empty.
- Access to the `<head>` of your site template, on every page — landing pages as well as form pages.
- The `<form>` markup you want to track, and an id on it if you have several forms.
- To forward your own handler’s data to MarqIQ, your API Key — MarqIQ creates it the first time you open Tracking Script, so there is nothing to request. It begins `miq_`. Route 2 on its own needs no key.
Which route you need
Pick one row. Both routes can run on the same site, but a single form should follow one of them.
| What you want | Route | What you install |
|---|---|---|
| Form fills in MarqIQ’s reports, attributed to a campaign | Route 1 | The MarqIQ tracking script. No hidden fields needed. |
| The campaign values inside the post your own handler receives | Route 2 | A self-contained snippet plus eight hidden inputs. |
| Both | Route 2, then forward | Route 2, plus a POST from your handler to MarqIQ. |
Do not mix the two sets of names
The bare names `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`, `entry_url`, `current_url` and `referral_url` belong to Route 2 and are filled by its own snippet. The MarqIQ tracking script does not fill them — it fills `utm_source_most_recent`, `first_utm_source` and the rest of the names listed in Which fields the script fills. Put Route 2’s eight names on a page carrying only the MarqIQ script and every one of them stays empty.
Route 1 — record the fills in MarqIQ
The tracking script watches for a form submit anywhere on the page and reports it to MarqIQ on its own. Your form needs no hidden fields at all for this: the campaign values come from the visitor’s MarqIQ cookies, not from the form.
Open Tracking Script in the sidebar.
It opens Install & deploy. The page itself is headed Script & Deploy.
Find the card headed CDN Install.
Click Copy tag.
You get one `<script>` line whose address ends in your API Key. That key is public by design — it sits in the page source of every site running the tag.
Paste the tag immediately before the closing `</head>` tag in your site template.
Publish the template.
Load one of your pages with `?utm_source=test&utm_medium=test` on the end of the address.
Fill in your form and submit it.
Put a real-looking email address in it. A fill with no recognisable email address is still stored and tied to the anonymous visitor id instead, which holds for a year in that one browser. An email address is what ties the same person together across browsers.
Open Home in the sidebar.
Your fills and the Script Performance panel are there on every plan. While your tag is live and nothing has arrived for 30 days, Home says so at the top of the page rather than showing an empty chart.
What MarqIQ stores for each fill
- Most recent source, medium, campaign, term and content — the visit the form was submitted on. Stored values are kept for 7 days from the last visit MarqIQ could attribute.
- First-touch source, medium, campaign, term and content, plus the first entry URL and the referring address. These are locked on the first visit and kept for a year.
- The address of the page the form was on.
- An anonymous visitor id, minted once per browser and kept for a year.
- The email address and a name, read out of the form itself.
- The form’s id, so several forms on one site stay apart.
How the email and name are found
MarqIQ takes the first input in the form that is either of type `email` or carries "email" anywhere in its name. For the name it looks for inputs whose names contain firstname, first_name or first-name and lastname, last_name or last-name, and falls back to an input named name, fullname or full_name. Capitals do not matter. An input named anything else is not read.
One fill per form per page load
Once a form has been reported, MarqIQ marks it and does not report it again until the page reloads. A visitor who submits, fails your server-side validation and submits again on the same page produces one row, not two.
Take care
For a plain HTML form the submit event is the signal, not your server’s answer. A fill your own handler rejects is still recorded. Forms rendered by HubSpot and Marketo are treated differently, because those engines report their own outcome.
Note
With Wait for consent before sending anything to MarqIQ switched on under Wait for cookie consent, no cookie is written and no fill is reported until the visitor’s banner signals consent. Global Privacy Control and Do Not Track are always honoured as an opt-out.
Route 2 — put the values in your own form
This route is self-contained. Its snippet reads the campaign parameters off the address, keeps them in `sessionStorage`, and writes them into any hidden input it recognises so they travel with the post your own code receives.
Open Tracking Script in the sidebar and choose HTML forms guide.
The page is headed HTML Forms Setup Guide. It is also on the HTML Forms card under Integrations, where the link reads View setup guide →.
Read the two tables under Step 1 — Field reference.
Click Copy on the block labelled UTM auto-populate script.
Paste that snippet inside the `<head>` of every page that carries a form.
A global site header works. The snippet also records the entry URL and the referring address, so a page a visitor lands on needs it even when that page has no form.
Click Copy on the block labelled Form with hidden UTM inputs.
Add the eight hidden inputs to your own form.
Keep every `name` attribute exactly as written.
The snippet finds inputs by name and nothing else. Rename one and it stays empty, with no error anywhere.
Publish the page.
Read the eight values in your form handler, the same way you read the visible fields.
| Input name | What it holds | Where it comes from |
|---|---|---|
| utm_source | Where this visit came from | The address, kept for the session |
| utm_medium | How it reached them | The address, kept for the session |
| utm_campaign | The campaign | The address, kept for the session |
| utm_term | The keyword bought | The address, kept for the session |
| utm_content | Which link inside the campaign | The address, kept for the session |
| entry_url | The first page of this session | Recorded once, on the session’s first page |
| current_url | The page the form sits on | Read at the moment the field is filled |
| referral_url | The site the visitor arrived from | `document.referrer`, recorded once per session |
The eight names the HTML Forms Setup Guide snippet fills. Every matching input on the page is filled, so two forms on one page both carry the values.
This snippet remembers a session, not a person
It keeps its values in `sessionStorage`, which the browser clears when the tab closes. A visitor who arrives from an advert today and returns tomorrow through a bookmark submits a form with nothing in `utm_source`, and `entry_url` shows tomorrow’s page. Route 1’s cookies hold a first touch for a year, which is the difference between the two.
Tip
`referral_url` is empty for direct traffic and for any referring site that strips the header. An empty value there is normal, not a fault.
Sending your own handler’s data to MarqIQ
Route 2 alone puts nothing in MarqIQ. To have those fills appear in your reports as well, post them from your handler. The screen that gives you the address and the key is Developer setup guide, under Tracking Script in the sidebar.
Open Tracking Script in the sidebar and choose Developer setup guide.
The page is headed Reports Setup Guide.
Click Copy beside the key in the card headed Get your API key.
Copy the address printed on the code block in Forward submission data to MarqIQ.
The block’s label is the word POST followed by the full address for your environment. Copy it from the screen rather than typing it.
Rotating the key invalidates the previous one at once, on your website tag and on every handler posting to MarqIQ. Rotate only if the key has been exposed somewhere it should not be.
Send a POST to that address from your form handler, with a JSON body.
Set the `x-api-key` header to your key.
A browser-side call that cannot set headers may send the same value as an `api_key` field in the body instead.
Name each value in the body as the code block names it.
MarqIQ reads `email`, `name`, `visitor_id`, `form_id`, the five `utm_` values, the five `first_utm_` values, `entry_url`, `current_url` and `referral_url`. Anything else in the body is discarded without comment.
Submit one test fill.
Check the reply reads `{"ok":true}`.
Limits on what you post
- The whole body must be 8 KB or smaller. Anything larger is refused with "Payload too large".
- Any single value is trimmed and cut to 512 characters. The three URL values are cut to 2,048, and `visitor_id` to 64.
- One IP address may send 600 requests a minute. Past that MarqIQ answers "Too many requests".
- An email address is lower-cased before it is stored, so the same person written two ways is one person.
- The tracking script cuts every URL it stores to 255 characters, so a URL posted by hand can be longer than one the script would have sent.
The earliest first touch wins
The `first_utm_` values you post are treated as a claim, not as fact. MarqIQ looks for the earliest fill it already holds for that email address — or, failing an email, for that visitor id — and keeps the first touch recorded there. A person’s first touch can be learned once and is never rewritten by a later post.
What MarqIQ writes, and what it does not do
- MarqIQ writes nothing to your website and nothing to your form. Route 2’s snippet fills inputs in the visitor’s own browser; no request leaves your page because of it.
- There is no sync. HTML Forms is not one of the tools MarqIQ pulls records from, so there is no Sync now button and no schedule to set.
- There is no audit report and no analysis suite for HTML Forms. Those run on CRM and marketing-automation tools, and a form on your own site is neither.
- Field Mapping and the form operations under Tool Operations do not list HTML Forms. Field mapping applies to tools that hold contact records, and your own form holds none.
- Your fills feed Home on every plan. They feed the Attribution & Journeys screens — channel, campaign, journey and funnel, under whichever attribution model you pick — from Pro upwards.
Note
A fill past your plan’s monthly allowance is stored anyway and marked, never dropped. The reply tells you it happened and your usage figures stay honest.
If it did not work
The hidden fields submit empty, and the MarqIQ tag is on the page
Why: The two routes were mixed. Your form carries Route 2’s bare names but the page carries only the MarqIQ tracking script, which fills `utm_source_most_recent`, `first_utm_source` and the other names in Which fields the script fills — never the bare eight.
Fix: Add the UTM auto-populate script snippet from HTML forms guide to the page head. The other way out is to rename the inputs to names the tracking script does fill, listed in Which fields the script fills. Field mapping is not a third way: it maps fields inside a connected CRM or marketing-automation tool, and it does not list HTML Forms.
MarqIQ answers your handler "Invalid API key" with a 401
Why: The key in the `x-api-key` header, or in the body’s `api_key` field, matches no workspace. Rotating the key on Script & Deploy or on Reports Setup Guide invalidates the previous one immediately.
Fix: Copy the current key from Get your API key on Reports Setup Guide and put it in your handler. Update your website tag at the same time, because a rotated key breaks that too.
"Missing api key (x-api-key header or api_key field)"
Why: The request arrived with neither the header nor the body field. A proxy that strips custom headers is a common cause.
Fix: Set the `x-api-key` header. Where the caller cannot set headers, send the same value as an `api_key` field inside the JSON body instead.
"Invalid email format" and the fill is not stored
Why: The address failed the check MarqIQ makes before storing anything: text, an @, text, a dot, text. The whole fill is refused rather than stored without its email address.
Fix: Trim the value in your handler before you post it, and drop the `email` key entirely rather than posting a placeholder. A fill with no email is accepted and tied to the visitor id instead.
"Payload too large", or "Invalid JSON body", or "Body must be a JSON object"
Why: The body is over 8 KB, is not valid JSON, or is an array or a bare string rather than an object. MarqIQ checks the declared length before it reads anything.
Fix: Post only the field names listed on Forward submission data to MarqIQ. Sending your whole form payload, file uploads and message bodies included, is what pushes a request past 8 KB.
"Too many requests" with a 429
Why: More than 600 requests reached MarqIQ from one IP address inside a minute. A server-side handler posts from one address for every visitor, so a busy site hits this long before a browser would.
Fix: Queue the posts in your handler and spread them, or post from the browser so the requests come from many addresses. Nothing is stored for a refused request, so retry the ones you held back.
The reply says "Stored, but this workspace is past its 10,000 submission/month Starter allowance."
Why: You have passed the monthly allowance for your plan. Nothing is being dropped — the row is stored and marked so your usage figures stay accurate.
Fix: Nothing, if you are content to run over. To clear the overage, move up a plan from Pricing. The message names your own limit and plan, so a Pro workspace sees 100,000 in the same sentence.
"Subscription expired — submissions are paused. Renew under Account settings › Subscription to resume tracking."
Why: The workspace’s subscription has lapsed. Capture stops on both routes: posts are refused with a 402, and the tag on your site downloads a file reading `/ MarqIQ: subscription expired — visit /pricing to renew /` instead of the script.
Fix: Renew from Pricing. Fills made while it was paused are gone, because nothing else holds them — renew before a campaign rather than after it.
The script address downloads `/ MarqIQ: invalid token /`
Why: The key in your tag no longer exists — almost always because it was rotated and the site was never updated.
Fix: Click Copy tag on Script & Deploy and replace the whole line in your template. The address ends in the key, so copying the key alone is not enough.
A suspended or read-only workspace stops recording fills
Why: MarqIQ answers "Workspace suspended — submissions are paused. Contact support." with a 403, or "Workspace is read-only — submissions are paused." with a 402, and serves a matching comment in place of the script.
Fix: Contact support. A MarqIQ administrator sets both states on your workspace, so neither one clears by renewing or by changing plan. A lapsed subscription is a different thing, and says "Subscription expired" instead.
Fills arrive with no campaign on them at all
Why: The tag is on your form page but not on the pages people land on, so nothing was recorded at the moment the campaign was knowable. Route 2 has the same failure: its snippet records the entry URL only on the session’s first page.
Fix: Put the tag, or the snippet, in a template that every page uses. Then check Why your traffic shows as Direct for the causes that survive a correct install.
A second fill on the same page never appears
Why: The tracking script reports a given form once per page load. Two fills with no reload in between — a single-page form that clears itself, for instance — collapse into one row.
Fix: Reload the page after a successful fill, or post the second fill from your handler yourself. A post from your handler is not caught by this: both guards — once per page load, and one repeat of the same form id and email address inside 10 seconds — live in the tracking script, not in what receives your posts.
Next steps
Related articles
Was this article helpful?