Reference
Node reference
Checked against n8n 2.47.3 node definitions. Workflow settings use execution order v1 and the Asia/Manila time zone.
#Main workflow nodes
| # | Node | Type | What it does |
|---|---|---|---|
| 1 | Website Form Webhook | Webhook v2.1 | POST /webhook/lead-capture. Responds through the Respond to Webhook node. Accepts JSON or form bodies; UTM tags in the query string are merged in. |
| 2 | When Executed by Another Workflow | Execute Workflow Trigger v1.3 | Input data mode: Accept all data. Receives leads from the Demo Form or any parent workflow and feeds the same path. |
| 3 | Config | Edit Fields (Set) v3.4 | The only node you edit. Every setting in one place. Passes the trigger data through. |
| 4 | Normalize & Validate | Code v2 | Maps field aliases, cleans text, lower-cases email, writes phones as E.164 (+639…), runs spam checks, checks required fields, guards against formula injection and pre-builds the reply for rejected leads. |
| 5 | Valid Lead? | If v2.2 | accepted = true continues. Otherwise skips to the reply (spam 200, invalid 422). |
| 6 | Read Existing Leads | Google Sheets v4.5, Get row(s) | Reads the Leads tab. Always Output Data is on, so an empty Sheet works. Retries 3 times; its error output goes to the save-failed path. |
| 7 | Dedupe Check | Code v2 | Matches by email (case-insensitive), then by phone. Builds lead_id (LC-YYYYMMDD-XXXX), timestamps in the business time zone, status, submission_count, notes and should_ack. |
| 8 | Duplicate? | If v2.2 | True goes to Update, false to Append. |
| 9 | Update Lead Row | Google Sheets v4.5, Update | Matches on lead_id and writes only last_seen_at, status, message, submission_count, trigger and notes. Retries 3 times. |
| 10 | Append Lead Row | Google Sheets v4.5, Append | Writes all 19 columns as RAW, so +63… stays text. Retries 3 times. |
| 11 | Build Response & Alerts | Code v2 | Builds the JSON reply, the HTML thank-you email and the alert text for each channel (Telegram HTML, Slack mrkdwn, Discord markdown with @everyone defused). |
| 12 | Build Save-Failed Response | Code v2 | Only when Sheets failed after retries: a 503 reply with a friendly message. |
| 13 | Webhook Request? | If v2.2 | trigger = webhook goes to the reply. Calls from other workflows skip it. |
| 14 | Respond to Website | Respond to Webhook v1.4 | Body from $json.response, status from $json.http_status (200, 422 or 503). |
| 15 | Save Failed? | If v2.2 | True stops the run with an error. |
| 16 | Stop: Lead Not Saved | Stop and Error v1 | Fails the run on purpose so the error handler logs and alerts it. |
| 17 | Send Acknowledgement? | If v2.2 | Uses should_ack: send_ack is on, the lead has an email, and it's not a repeat unless ack_on_duplicate is on. |
| 18 | Gmail: Acknowledge Lead | Gmail v2.1, Send | HTML email with the lead's first name, message and reference ID. Errors go to the Error Log. |
| 19 | Route Alerts | Switch v3.2 | Sends to every channel that's switched on (all matching outputs). |
| 20 | Telegram: Alert Team | Telegram v1.2 | HTML parse mode, link previews off. |
| 21 | Slack: Alert Team | Slack v2.3 | Posts to slack_channel_id. |
| 22 | Discord: Alert Team (HTTP) | HTTP Request v4.2 | POST to a Discord webhook URL, with mentions disabled. |
| 23 | Log Notification Failure | Google Sheets v4.5, Append | Writes failed email and alert attempts to Error Log, with an execution link. |
#Error handler and Demo Form
| Workflow | Nodes |
|---|---|
| Lead Capture → Error Handler | Error Trigger v1 → Error Config → Log Error to Sheet, plus Alert on Error? → Telegram: Error Alert. Publish it, then select it in the main workflow's Error Workflow setting. |
| Lead Capture → Demo Form | Demo Lead Form (Form Trigger v2.3, path lead-capture-demo, Ignore Bots on) → Save Lead (run main workflow) (Execute Workflow v1.3, source Database, wait on) → Thank-You Page or Error Page (Form v2.3, Form Ending). |
The Demo Form asks for Full Name, Email, Phone / Mobile, Company / Business and How can we help?, plus hidden source, utm_source, utm_medium and utm_campaign fields filled from the link, for example ?utm_source=facebook.
#Config fields
| Field | Type | Default | Meaning |
|---|---|---|---|
business_name |
string | Your Business |
Shown in the thank-you email and alerts |
sheet_id |
string | placeholder | Google Sheet ID from its address |
leads_tab |
string | Leads |
Tab that stores leads |
error_tab |
string | Error Log |
Tab for failed email and alert attempts |
timezone |
string | Asia/Manila |
Time zone for received_at and last_seen_at |
n8n_base_url |
string | https://n8n.example.com |
Your n8n address, for execution links |
default_country_code |
string | 63 |
Country code for local numbers |
default_source |
string | website |
source when a request sends none |
form_source_label |
string | n8n-demo-form |
source for the hosted form |
form_thank_you_text |
string | Thanks! We received your message and will reply soon. |
formSubmittedText in the reply |
send_ack |
boolean | true |
Send the thank-you email |
ack_on_duplicate |
boolean | false |
Also email repeat leads |
ack_from_name |
string | Your Business |
Sender name on the email |
ack_subject |
string | Thanks for contacting [business] |
Subject; [business] is replaced |
ack_response_time |
string | will get back to you within 1 business day |
The promise in the email |
ack_signature |
string | — The Your Business team |
Last line of the email |
alert_telegram |
boolean | true |
Send Telegram alerts |
alert_slack |
boolean | false |
Send Slack alerts |
alert_discord |
boolean | false |
Send Discord alerts |
alert_on_duplicate |
boolean | true |
Alert for repeat leads too |
telegram_chat_id |
string | placeholder | Chat or group ID (groups start with -100) |
slack_channel_id |
string | placeholder | Slack channel ID (C…) |
discord_webhook_url |
string | empty | Discord webhook URL; keep it private |
dedupe_by_phone |
boolean | true |
Treat the same phone as the same lead |
keep_status_on_duplicate |
string | contacted,qualified,won |
Statuses kept when a lead returns; others become returning |
honeypot_field |
string | website |
Hidden field that must stay empty |
min_fill_seconds |
number | 3 |
Minimum seconds between opening and submitting the form |
max_links_in_message |
number | 2 |
More links than this counts as spam |
spam_keywords |
string | casino, crypto investment, forex signals, seo services, backlinks, … |
Comma list; any match is spam |
blocked_email_domains |
string | mailinator.com, guerrillamail.com, 10minutemail.com, tempmail.com, yopmail.com |
Throw-away email domains |
#Data each step adds
| Field | Example | Notes |
|---|---|---|
accepted |
true |
Not spam and no validation errors |
is_spam, spam_reasons |
true, ["honeypot"] |
Reasons: honeypot, too_fast, too_many_links, keyword:<word>, blocked_domain:<domain> |
errors |
["email looks invalid"] |
name is required, email or phone is required, email looks invalid, phone looks invalid, name is too long |
trigger |
webhook, form or subworkflow |
webhook only when the workflow's own webhook node ran. Only webhook reaches Respond to Website. |
name, company, message |
Trimmed. Cells starting with =, +, - or @ get a leading ' so Sheets won't run them as formulas. |
|
email, phone |
maria.santos@example.com, +639170000101 |
Email lower-cased; phone in E.164 |
Dedupe Check adds lead_id, received_at, last_seen_at, status, submission_count, notes, is_duplicate, matched_by, existing_row_number and should_ack. Use them in any node you add later:
{{ $('Dedupe Check').first().json.lead_id }}#Sheet columns
Leads row 1, in this order:
lead_id, received_at, last_seen_at, status, name, email, phone, company, message, source, utm_source, utm_medium, utm_campaign, utm_term, utm_content, page_url, submission_count, trigger, notes| Column | New lead | Repeat lead |
|---|---|---|
lead_id |
Written | Used to find the row |
received_at |
First contact | Kept |
last_seen_at, message, trigger |
Written | Updated |
status |
new |
returning, or kept if already contacted, qualified or won |
name, email, phone, company |
Written | Kept (first values win) |
source, utm_*, page_url |
Written | Kept (first-touch attribution) |
submission_count |
1 |
+1 |
notes |
Written | “Re-submitted …” appended |
Error Log row 1: logged_at, workflow, execution_id, execution_url, failed_node, error_message, lead_id.
Status values: new → contacted → qualified → won or lost. returning means the person sent the form again.
#Error handling
| Failure | What happens |
|---|---|
| Google Sheets read, append or update fails | 3 retries, 2 seconds apart → 503 reply → Stop: Lead Not Saved → the error handler logs it to Error Log and alerts Telegram |
| Gmail send fails | 3 retries → a row in Error Log. The lead is already saved. |
| Telegram, Slack or Discord fails | 3 retries → a row in Error Log |
| Writing to the Error Log fails | Ignored, so it never hides the original result |
| Any other unexpected error | n8n fails the run → the error handler, if it's selected in settings |