<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alec Asgari</title>
    <description>The latest articles on DEV Community by Alec Asgari (@alecasgari).</description>
    <link>https://dev.to/alecasgari</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4208566%2Ff18c141f-26b7-478a-a63f-917620372482.png</url>
      <title>DEV Community: Alec Asgari</title>
      <link>https://dev.to/alecasgari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alecasgari"/>
    <language>en</language>
    <item>
      <title>How I automate lead intake with n8n, Cloudflare Turnstile and Telegram</title>
      <dc:creator>Alec Asgari</dc:creator>
      <pubDate>Mon, 26 Oct 2026 11:26:43 +0000</pubDate>
      <link>https://dev.to/alecasgari/how-i-automate-lead-intake-with-n8n-cloudflare-turnstile-and-telegram-2mc3</link>
      <guid>https://dev.to/alecasgari/how-i-automate-lead-intake-with-n8n-cloudflare-turnstile-and-telegram-2mc3</guid>
      <description>&lt;p&gt;Most small-business websites still treat the contact form as an afterthought. A visitor fills it in, and an email lands in a shared inbox where it waits next to newsletters and invoices. Someone notices it hours later, or never. Meanwhile, half the messages are spam.&lt;/p&gt;

&lt;p&gt;When I rebuilt my own site, I replaced that whole chain with one n8n workflow. Every real enquiry now gets stored in a spreadsheet and pings my phone on Telegram within a few seconds. Bots get rejected before they cost me anything. Here is exactly how it is built, so you can copy the pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the workflow
&lt;/h2&gt;

&lt;p&gt;The workflow has fourteen nodes, but the logic is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Webhook&lt;/strong&gt;: receives the form POST from the website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalize&lt;/strong&gt;: a Code node that trims and cleans the fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honeypot check&lt;/strong&gt;: drops obvious bots quietly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt;: rejects incomplete submissions with a clear error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turnstile verification&lt;/strong&gt;: asks Cloudflare whether the visitor is human.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save to Google Sheets&lt;/strong&gt;: one row per lead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram message&lt;/strong&gt;: an instant notification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respond&lt;/strong&gt;: tells the website whether it worked, so the visitor sees a thank-you page or an error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step does one job, and the order matters. The cheap checks come first and the external calls come last.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: a webhook that only does one thing
&lt;/h2&gt;

&lt;p&gt;The site is a static build on Cloudflare Pages, so there is no backend of its own. The form posts JSON straight to an n8n Webhook node set to "respond using a Respond to Webhook node". That setting is important. The browser waits for n8n to decide, which means you can return a real &lt;code&gt;200&lt;/code&gt;, &lt;code&gt;400&lt;/code&gt; or &lt;code&gt;403&lt;/code&gt; instead of always saying "thanks".&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: normalize before you check anything
&lt;/h2&gt;

&lt;p&gt;Forms send messy data: trailing spaces, uppercase emails, empty strings that should be missing. A short Code node fixes that once, so every later node can trust the shape of the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;      &lt;span class="c1"&gt;// honeypot field&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cf-turnstile-response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;}}];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: the honeypot, the cheapest spam filter there is
&lt;/h2&gt;

&lt;p&gt;The form has a hidden field that real people never see. Bots that blindly fill every input fill it in too. If the honeypot is not empty, the workflow returns a normal &lt;code&gt;200&lt;/code&gt; and stops. The bot thinks it succeeded and moves on, and nothing reaches the sheet or my phone. It costs zero API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: validate and fail loudly
&lt;/h2&gt;

&lt;p&gt;If the name, email or message is missing, or the email does not look like an email, the workflow responds with &lt;code&gt;400&lt;/code&gt;. The front end shows "please check the form" instead of pretending everything went fine. Silent failures are the main reason small businesses lose leads without ever knowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Cloudflare Turnstile, verified on the server
&lt;/h2&gt;

&lt;p&gt;Turnstile is Cloudflare's free CAPTCHA alternative. Most visitors never click anything. The widget in the browser produces a token, but &lt;strong&gt;the token means nothing until your server checks it&lt;/strong&gt;. An HTTP Request node posts the token and the secret key to &lt;code&gt;https://challenges.cloudflare.com/turnstile/v0/siteverify&lt;/code&gt;. An IF node then reads &lt;code&gt;success&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two practical tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the secret in a Set ("Config") node or in n8n credentials, never in the front end.&lt;/li&gt;
&lt;li&gt;If the check fails, return &lt;code&gt;403&lt;/code&gt;. Real users almost never see it, but headless browsers and scripted tests often will. I learned that while testing my own form from an automated browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 6: store every lead somewhere boring and reliable
&lt;/h2&gt;

&lt;p&gt;A Google Sheets node appends a row with the timestamp, name, email, message and the page the visitor came from. A spreadsheet is not a CRM, but it is searchable, shareable and impossible to lose. Later you can add a node that pushes the same data into SuiteCRM, HubSpot or whatever you use, without touching the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Telegram, because email is where leads go to die
&lt;/h2&gt;

&lt;p&gt;The last action is a Telegram node that sends me a short message: who, what, and a link to the sheet. I see it on my phone within seconds, wherever I am. For a one-person business, replying in ten minutes instead of the next morning is the cheapest conversion boost there is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: tell the browser what happened
&lt;/h2&gt;

&lt;p&gt;Every branch ends in a Respond to Webhook node: &lt;code&gt;200&lt;/code&gt; for success (and for the honeypot), &lt;code&gt;400&lt;/code&gt; for invalid input, &lt;code&gt;403&lt;/code&gt; for a failed Turnstile check. The front end turns these into a thank-you redirect or a clear error message. You can also fire a &lt;code&gt;generate_lead&lt;/code&gt; event to Google Analytics only on a real &lt;code&gt;200&lt;/code&gt;, so your conversion numbers are honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs and what it saves
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; Turnstile is free, Telegram is free, and Google Sheets is free. n8n runs self-hosted on a small server I already pay for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saves:&lt;/strong&gt; no spam in the inbox, no missed enquiries, and no paid form tool with a monthly fee per seat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership:&lt;/strong&gt; the workflow, the data and the server are mine. If I change the website, the workflow stays the same.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Make it yours
&lt;/h2&gt;

&lt;p&gt;This pattern works for any static site: Astro, Next.js, Hugo or plain HTML. Start with the webhook, honeypot and Telegram nodes, which take about 30 minutes. Then add Turnstile and storage. Once leads arrive reliably, you can add AI steps, such as classifying the enquiry or drafting a first reply.&lt;/p&gt;

&lt;p&gt;I build workflows like this for small businesses in the Netherlands and across the EU. If you would rather not wire it up yourself, here is what an &lt;a href="https://alecasgari.com/services/n8n-automation" rel="noopener noreferrer"&gt;n8n automation project&lt;/a&gt; with me looks like, or see &lt;a href="https://alecasgari.com/projects" rel="noopener noreferrer"&gt;more of my work&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>webdev</category>
      <category>smallbusiness</category>
    </item>
  </channel>
</rss>
