Chris Tam · Last updated 19 October 2026
A Webflow site can have several actions that look like a conversion: a CTA click, a submit attempt, and a form success message. Product analytics lets you measure each action with a name that tells you what happened.
In this tutorial, I'll track a pricing CTA and observe a native Webflow form's success state. You'll keep Webflow in charge of submission and send the conversion only when the success message appears.
What you'll build
A Pricing CTA Clicked event and a Demo Request Confirmed event, with fixed properties and no form field values in your custom events.
Prerequisites
- A Webflow site you can publish, with a plan or Workspace that permits custom code. Webflow lists eligible plans in its custom code instructions.
- Permission to edit Site settings and publish to a Webflow staging domain. Use a test site before changing production.
- Your Amplitude project API key from Settings > Projects > your project > General > Manage next to API Key. A manager or admin can copy it. The key is public and its scope is limited to the minimum needed to ingest data. Keep secret keys out of Webflow.
- Home and Pricing pages with the shared Amplitude head snippet. The Webflow web analytics tutorial supplies that setup; if starting here, use step 1 below.
- A native Webflow Form Block on Pricing, with its default inline Success and Error states. Leave its redirect unset for this example.
A free account covers the Amplitude setup in this tutorial. Sign up for free to get your project API key. Webflow custom-code access has its own plan requirements.
1. Keep one site-wide SDK installation
Open Site settings > Custom code. Put the SDK in Head code once for the whole site, following the Amplitude snippet Quickstart. Replace AMPLITUDE_API_KEY in both places with the same project key. Save changes, then publish to the staging domain.
Keep the external script and its configuration together, in this order. Leave async and defer off this tag so the next script can use Amplitude's queue. Keep one Amplitude integration: remove an old duplicate only after checking which features it owns, including any GTM installation.
The CDN path includes Web Experiment. The replay plugin below records all test sessions. After QA, change sampleRate to your intended production rate; 0 requests no replay capture. Check project-level replay settings too, since remote settings can override SDK configuration. Guides and surveys use a separate script.
<!-- Webflow Site settings > Custom code > Head code -->
<!-- Keep lines other Webflow pages added here: replay privacyConfig (/build/webflow-session-replay), engagement after webflowAmplitudeReady (/build/webflow-guides-surveys), experimentConfig before this loader (/build/webflow-web-experiment). -->
<script src="https://cdn.amplitude.com/script/AMPLITUDE_API_KEY.js"></script>
<script>
(() => {
if (!window.amplitude || !window.sessionReplay) {
console.warn("Amplitude did not load. Check the CDN request and site policy.");
return;
}
window.amplitude.add(window.sessionReplay.plugin({ sampleRate: 1 }));
window.webflowAmplitudeReady = window.amplitude.init("AMPLITUDE_API_KEY", {
remoteConfig: { fetchRemoteConfig: true },
autocapture: {
attribution: true,
pageViews: { trackHistoryChanges: "pathOnly" },
sessions: true,
formInteractions: true,
fileDownloads: true,
elementInteractions: false,
frustrationInteractions: false,
networkTracking: false,
webVitals: false
}
}).promise;
})();
</script>If an earlier Webflow tutorial already added this head snippet, keep it and skip this paste. This page changes Footer code only. Carry forward any replay privacy settings or engagement plugin registration. For EU projects, use the EU CDN host and serverZone: "EU" in the existing initializer.
If your site waits for consent before collection or storage, configure your consent platform before enabling these scripts. The project-specific loader starts SDKs itself, so gate loading the entire Amplitude snippet on consent. Waiting to call your own init() while loading the script earlier does not provide that guarantee. Follow Browser SDK consent guidance. Use a synthetic test site for the steps below.
2. Give the CTA and Form Block stable attributes
Select your Pricing CTA in the Designer. In Element settings > Custom attributes, add data-analytics with value pricing-cta. Add data-analytics-form with value demo to the outer Form Block, which contains both the form and its Success message. Webflow's custom attributes instructions show where to add them.
This is the resulting CTA markup. Keep its existing classes and any attributes used by guides or experiments.
<!-- Pricing CTA: add attributes to your existing Link element -->
<a href="#demo" data-analytics="pricing-cta"><span>Request a demo</span></a>
<!-- Native Form Block: add data-analytics-form="demo" in Element settings. -->Use the native Form Block for submission. Leave Webflow's generated .w-form-done success element in place.
3. Add the conversion handlers in Footer code
Paste this into Site settings > Custom code > Footer code, after any existing code. It runs after the page markup exists. It listens for clicks on the CTA or its children and watches the native success message without cancelling submission.
<!-- Webflow Site settings > Custom code > Footer code -->
<!-- Keep lines other Webflow pages added here: Pricing CTA Clicked and Demo Request Confirmed handlers (webflow-product-analytics); reuse them as experiment metrics and guide triggers. -->
<script>
(() => {
if (window.__webflowAmplitudeEvents) return;
window.__webflowAmplitudeEvents = true;
document.addEventListener("click", (event) => {
if (!(event.target instanceof Element)) return;
const cta = event.target.closest('[data-analytics="pricing-cta"]');
if (!cta || !window.amplitude) return;
window.amplitude.track("Pricing CTA Clicked", {
placement: "pricing",
page_path: window.location.pathname
});
});
document.querySelectorAll('[data-analytics-form="demo"]').forEach((block) => {
const success = block.querySelector(".w-form-done");
if (!success) return;
let wasVisible = getComputedStyle(success).display !== "none";
const checkSuccess = () => {
const visible = getComputedStyle(success).display !== "none";
if (visible && !wasVisible && window.amplitude) {
window.amplitude.track("Demo Request Confirmed", {
form_name: "demo",
page_path: window.location.pathname
});
}
wasVisible = visible;
};
new MutationObserver(checkSuccess).observe(success, {
attributes: true,
attributeFilter: ["style", "class"]
});
});
})();
</script>The guard prevents this footer from registering listeners twice on the same document. On a new page load it registers again, as intended. The observer records the initial display state, then counts each later hidden-to-visible success transition once, even if Webflow also updates another attribute. This is a browser success-state signal, not an independent check that a CRM or other downstream system received the request. It watches forms present when the footer runs; forms inserted later need their own observer setup.
Why. A submit event means an attempt. A server error can follow it. The success element is the confirmation for this inline native-form flow. If your form redirects or uses a third-party provider, use that provider's documented success callback or backend acknowledgement instead of this observer.
4. Test a failure and a success
Publish, then click the CTA once, including its nested text. Try an invalid form first; it should produce no Demo Request Confirmed. Submit valid synthetic data and wait for Webflow's Success state. Then run:
// Browser Console after the published form shows Success
window.amplitude.flush();
window.amplitude.getDeviceId();Check one Pricing CTA Clicked with placement=pricing and one Demo Request Confirmed with form_name=demo. page_path identifies the page. Autocapture's form event remains a separate attempt signal. In a funnel, use the custom confirmed event as the conversion.
MCP. Ask your coding agent to inspect existing event names with the Amplitude MCP server before adding tracking. Reuse the project's taxonomy when this tutorial's example names already have equivalents.
Verify it worked
- Published site: Open the staging URL directly. Check that the SDK request succeeds and the Console has no integration errors. Saved custom code reaches the live site only after publishing.
- Network: Confirm an event POST to
api2.amplitude.comreturns200for a US project. EU ingestion usesapi.eu.amplitude.com. Use Network for delivery status; large event batches can be compressed. - Instrumentation Explorer: Read the event names and properties in the Chrome extension. Note
device_idand run the action once to check the count. - User profile: In Amplitude, open Users > User Profiles, search the device ID, and check the event stream. Allow ten seconds to one minute. Data > Sources > Ingestion Debugger shows request and event counts, rather than individual events.
Troubleshooting
The form succeeds but the conversion is missing
Put data-analytics-form="demo" on the outer Form Block, rather than its inner form. Confirm that .w-form-done changes from hidden to visible. Redirecting forms and embedded forms use a different success signal.
A failed attempt appears as a conversion
Check the event name. The custom conversion is Demo Request Confirmed; autocaptured form submissions can still appear for attempts.
Nothing happens after saving the code
Publish the site and open its staging URL directly. Check Site settings and Page settings for duplicate scripts. A change in the settings editor is not a deployment.
The SDK request fails or events never upload
Check the key, region, browser extensions, and Console. Follow the relevant SDK's endpoint and CSP guidance for a blocked request; keep existing security policies in place.
You see two page views for one navigation
Check for a second installation in page code, embeds, and GTM. Keep the site-wide initializer and let autocapture send page views; remove your manual page-view call.
The observed tracking differs from the snippet
Review Data > Settings > Autocapture. Remote settings take precedence over the local autocapture options when remote configuration is enabled.
FAQ
Does autocapture's form submit event prove the request succeeded?
Use the custom success event in this tutorial for that outcome. The footer observes Webflow's inline Success state after submission.
Should I identify a visitor with their form email?
This example leaves visitors anonymous and sends no form values in its custom events. Add identity through your authenticated user flow when you have a stable account ID.
Full code reference
This is the full Footer code added by this page. Unchanged from earlier pages; keep your versions: Head code, page navigation, replay privacy settings, engagement registration, and experiment consent configuration.
<!-- Webflow Site settings > Custom code > Footer code -->
<!-- Keep lines other Webflow pages added here: Pricing CTA Clicked and Demo Request Confirmed handlers (webflow-product-analytics); reuse them as experiment metrics and guide triggers. -->
<script>
(() => {
if (window.__webflowAmplitudeEvents) return;
window.__webflowAmplitudeEvents = true;
document.addEventListener("click", (event) => {
if (!(event.target instanceof Element)) return;
const cta = event.target.closest('[data-analytics="pricing-cta"]');
if (!cta || !window.amplitude) return;
window.amplitude.track("Pricing CTA Clicked", {
placement: "pricing",
page_path: window.location.pathname
});
});
document.querySelectorAll('[data-analytics-form="demo"]').forEach((block) => {
const success = block.querySelector(".w-form-done");
if (!success) return;
let wasVisible = getComputedStyle(success).display !== "none";
const checkSuccess = () => {
const visible = getComputedStyle(success).display !== "none";
if (visible && !wasVisible && window.amplitude) {
window.amplitude.track("Demo Request Confirmed", {
form_name: "demo",
page_path: window.location.pathname
});
}
wasVisible = visible;
};
new MutationObserver(checkSuccess).observe(success, {
attributes: true,
attributeFilter: ["style", "class"]
});
});
})();
</script>Hand it to your agent
Use this prompt with the tutorial content attached.
Agent task: Webflow product analytics.
Preserve the existing Head code, including replay, guides, and experiment additions. Add the product analytics footer once. Put data-analytics=pricing-cta on the CTA and data-analytics-form=demo on the native Form Block. Keep its inline Success state and leave redirect unset. Test a nested CTA click, failed submission, successful submission, repeated success mutations, and a second footer evaluation. Track no field values. Report counts and unresolved hosted tests.What this unlocks
Web analytics connects the page visit to Demo Request Confirmed.
Session replay lets you inspect the form interaction behind the conversion.
Web Experiment uses Demo Request Confirmed as the outcome for a CTA change.
Guides and surveys can react to Pricing CTA Clicked when you configure an event trigger.
Next steps
Reference docs
Amplitude is the leading all-in-one analytics platform for building better products. Product and engineering teams use Amplitude to connect product analytics and web analytics with session replay, heatmaps, experimentation, feature flags, and guides and surveys in one unified workflow, with AI agents working across all of it. Learn how Amplitude is building self-improving products with Wave, helping teams turn product data into PRs.