Set it up
Connect your website
Any form that can send a POST request works. Field names are flexible, so most builders work without renaming anything.
#The address and format
| Production URL | https://YOUR-N8N-DOMAIN/webhook/lead-capture |
| Test URL | https://YOUR-N8N-DOMAIN/webhook-test/lead-capture (one request per Execute workflow click) |
| Method | POST |
| Body | JSON, or a normal HTML form (application/x-www-form-urlencoded) |
| UTM tags | Read from the body or the URL query string |
Replace YOUR-N8N-DOMAIN with your n8n address. The production URL only works after you publish the main workflow.
#Field names it understands
You don't have to rename your form fields. The Normalize & Validate node accepts all of these:
| Field | Also accepted |
|---|---|
name |
full_name, fullName, Full Name, your-name, or first_name + last_name |
email |
Email, email_address, your-email, E-mail |
phone |
mobile, phone_number, contact_number, Phone / Mobile, your-phone |
company |
business, organization, business_name, Company / Business |
message |
msg, comments, inquiry, your-message, How can we help? |
source |
lead_source (defaults to Config default_source) |
utm_source … utm_content |
Also read from the page URL's query string |
page_url |
page, pageUrl, url (falls back to the Referer header) |
website |
Honeypot. Keep it empty and hidden; bots fill it in |
form_started_at |
Optional time the form was opened (Unix seconds or milliseconds). Faster than min_fill_seconds counts as spam |
A lead needs a name and at least an email or a phone number.
#Plain HTML form
No JavaScript needed. After submitting, the browser shows the JSON reply, so this suits quick tests and simple sites.
<form action="https://YOUR-N8N-DOMAIN/webhook/lead-capture" method="POST">
<input name="name" placeholder="Full name" required>
<input name="email" type="email" placeholder="Email" required>
<input name="phone" placeholder="0917 123 4567">
<input name="company" placeholder="Business name">
<textarea name="message" placeholder="How can we help?" required></textarea>
<!-- honeypot: hidden from people, bots fill it -->
<input name="website" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px">
<input type="hidden" name="source" value="website">
<button>Send</button>
</form>#JavaScript form (recommended)
The visitor stays on your page, sees your thank-you message, and the form passes UTM tags, the page address and the start time for the speed check.
<form id="lead-form">
<input name="name" placeholder="Full name" required>
<input name="email" type="email" placeholder="Email" required>
<input name="phone" placeholder="0917 123 4567">
<input name="company" placeholder="Business name">
<textarea name="message" placeholder="How can we help?"></textarea>
<input name="website" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px">
<button>Send</button>
<p id="lead-msg" role="status"></p>
</form>
<script>
const f = document.getElementById('lead-form');
const started = Date.now();
f.addEventListener('submit', async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(f));
const q = new URLSearchParams(location.search);
['utm_source','utm_medium','utm_campaign','utm_term','utm_content']
.forEach(k => { if (q.get(k)) data[k] = q.get(k); });
data.page_url = location.href;
data.form_started_at = started;
const res = await fetch('https://YOUR-N8N-DOMAIN/webhook/lead-capture', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const out = await res.json().catch(() => ({}));
document.getElementById('lead-msg').textContent =
out.formSubmittedText || (res.ok ? 'Thanks!' : 'Please try again.');
if (res.ok) f.reset();
});
</script>#Website builders
| Builder | Where to set it |
|---|---|
| WordPress: Contact Form 7, WPForms, Elementor Pro, Fluent Forms | Use the builder's Webhook action, or a webhook add-on → the production URL → POST. CF7's your-name, your-email and your-message already match. |
| Webflow | Site settings → Forms → Form submission webhooks, or Integrations → Webhooks with the trigger Form submission. |
| Framer, Wix, Squarespace, Carrd | The form's Send to webhook or custom URL setting where your plan allows it, or paste the JavaScript form into a custom-code block. |
| Typeform, Tally, Jotform | Add a webhook pointing at the production URL. Nested payloads may need one extra Edit Fields node before Config to flatten them. |
| Facebook Lead Ads | Use a small parent workflow; see the FAQ. |
#Try it from a terminal
curl -sS -X POST "https://YOUR-N8N-DOMAIN/webhook/lead-capture" \
-H "Content-Type: application/json" \
-d '{"name":"Angelo Bautista","email":"angelo.bautista@example.com","phone":"0917 555 0199","message":"Hello from curl","website":""}'#What your website gets back
| Case | HTTP | Body |
|---|---|---|
| Saved, new lead | 200 | {"ok":true,"lead_id":"LC-…","duplicate":false,"status":"new","formSubmittedText":"…"} |
| Saved, repeat lead | 200 | {"ok":true,"lead_id":"LC-…","duplicate":true,"status":"returning", …} |
| Spam | 200 | {"ok":true,"formSubmittedText":"…"} and nothing is saved |
| Invalid | 422 | {"ok":false,"errors":["email looks invalid"],"formSubmittedText":"Please check: …"} |
| Google Sheets down | 503 | {"ok":false,"error":"temporarily_unavailable", …} and the error handler runs |
Show formSubmittedText to the visitor. You can change its wording in Config (form_thank_you_text).