SkynetLabs
Back to journal
AutomationSeptember 21, 202612 min read

The n8n Speed-to-Lead Workflow: an AI Reply in About Two Seconds

By , Founder, SkynetLabs

An n8n speed-to-lead workflow answered a web form in 2.4s and 1.9s on 2026-09-17, then qualified the reply and proposed a visit window. MIT JSON.

On this page

This n8n speed-to-lead workflow answered a web form submission in 2.4 seconds and 1.9 seconds, measured on 2026-09-17. Anthropic Claude Haiku 4.5 drafts the first text, Claude Opus 5 reads the homeowner's reply, qualifies the job and proposes a visit window, and every state change is written to Postgres. The demo is simulated and the JSON is MIT-licensed.

The advice every home-services owner has already heard is a checklist: call within five minutes, text within ten, never let a form sit overnight. Nobody explains how you do that at 11pm on a Sunday, or during a job, or while you are on a roof. The five minutes is not the hard part. Being awake for it is.

Episode 02 of the n8n Workflows series makes the reply itself the automation rather than the rule. A homeowner fills a form and a text is already written and saved before the page has finished loading. They reply. A second, larger model reads what they said, decides whether it knows enough to book, and proposes a real visit window. The decision arrives as a bare JSON object, not as prose somebody has to parse. Nothing in the chain is allowed to invent a price, a warranty, an arrival time or a technician name.

If iframes are blocked where you are reading this, the video is at youtu.be/-lEQYzTgI5o.

Read this before anything else. The company in the demo, Lone Star Air, is fictional. Every page of the public demo is labelled SIMULATED. No SMS provider is wired into this workflow: the text message is a JSON string returned to the caller, meant to be drawn as a chat bubble on a demo page. This is a simulation built to be understood, not a production lead-routing system.

Specifications

Three public webhook lanes, 17 nodes, two Anthropic Claude models and three Postgres tables. A lead form creates a session and gets a reply. A homeowner reply is qualified and may create a booking. A page polls its own conversation by token.
Item Value
Workflow name Speed-to-Lead (simulated demo)
Nodes 17
Triggers POST /webhook/demo/lead, POST /webhook/demo/reply, GET /webhook/demo/state?token=
First-reply model Anthropic Claude Haiku 4.5, capped at 200 output tokens
Qualifying model Anthropic Claude Opus 5, capped at 300 output tokens
Storage Postgres: demo_sessions, demo_messages, demo_bookings
Messaging None. No SMS provider, no CRM contact, no send queue.
Session key md5(random || clock_timestamp()), generated in SQL rather than in the workflow
Public JSON includes All 17 nodes, both system prompts, every SQL statement and the slot-mapping code. Credentials stripped to REPLACE_ME, host examples read YOUR-N8N.
Licence MIT

How does the n8n speed-to-lead workflow reply in about two seconds?

It skips everything that normally sits between a form and a human. The webhook receives the form, one Code node cleans it, one SQL statement creates the session and returns a token, and one Anthropic call drafts a text of under 320 characters. The reply is saved to Postgres and returned in the same HTTP response, so the page can draw it immediately.

Lane one is the new lead. Clean Lead treats the body as hostile, because the endpoint is public: it strips control characters, clamps the name, phone, city and issue to fixed lengths, falls back to placeholders where a field is empty, and returns a 400 if there is no issue left after cleaning. Create Session inserts the row and returns the token. AI First Text casts Claude Haiku 4.5 as the dispatcher for a demo HVAC company and instructs it to use the first name, reference the stated problem, ask exactly one qualifying question, and never invent prices, warranties, technician names or arrival times.

Lane two is the homeowner's reply. Clean Reply whitelists the token to hex characters and clamps the text to 400 characters. Log Lead Text writes the new message and builds the transcript. AI Qualify + Book hands that transcript to Claude Opus 5 with one instruction that makes the whole lane deterministic: reply with bare JSON containing a message, a boolean for whether to book, a job type and a slot hint. The slot hint is one of four tokens, never a raw date. Parse Decision turns that hint into both a machine timestamp and a finished human string, and Save AI Reply writes the message, conditionally inserts the booking, and stamps the session the first time a booking happens.

Lane three is the page polling its own state by token, one Postgres query returning the message list and any booking as a single JSON object.

Lane Node What it does
Lead Web Lead In Webhook, POST /demo/lead. Accepts name, phone, city, issue.
Lead Clean Lead Code. Strips control chars, clamps every field, 400s on an empty issue.
Lead Create Session Postgres INSERT returning id, token, name, city, issue. Token generated in SQL.
Lead AI First Text Anthropic Claude Haiku 4.5. Under 320 characters, exactly one qualifying question.
Lead Save AI Text, Respond Lead Writes the message, returns ok, token and message to the page.
Reply Homeowner Reply In, Clean Reply Webhook plus a token whitelist and a 400-character clamp.
Reply Log Lead Text One statement: insert the message, then build the transcript by aggregation plus an explicit append. See the gotcha.
Reply AI Qualify + Book Anthropic Claude Opus 5. Returns bare JSON: message, book, job_type, slot_hint.
Reply Parse Decision Code. Maps the slot hint to a day plus a fixed four-hour window, renders the label server-side.
Reply Save AI Reply, Respond Reply Message plus at most one booking per session, then the JSON response.
State State In, Read State, Respond State One Postgres query by token returning messages and booking as JSON.

What is real in the public JSON and what is a placeholder?

Both system prompts, every SQL statement, the slot mapping and all 17 nodes are exactly what ran. Two credentials ship as REPLACE_ME: a Postgres credential and a header-auth credential carrying the Anthropic key as x-api-key. The curl examples read YOUR-N8N, which is your own instance hostname.

Two further things you should know before importing:

  • The model names come from environment variables with hardcoded fallbacks. CLASSIFIER_MODEL defaults to claude-haiku-4-5 and CLAUDE_MODEL_SONNET defaults to claude-opus-5. The second variable name is misleading and the fallback is genuinely Opus 5. Rename the variable or edit the fallback string if you want a different model on that lane.
  • The column types in schema.sql were derived from the SQL, not exported from a live database. They are sensible defaults rather than a verified dump, and the file says so. The unique index on demo_sessions.token is load-bearing: every lane after the first looks a session up by token.
  • The demo page is not in the repo. It is a static page making exactly three fetch calls against the contract above. Build your own.

How fast was it, measured?

Submit to first reply was 2.4 seconds and 1.9 seconds. Reply to booking was 4.35 seconds and 3.37 seconds. All four figures were observed on 2026-09-17 across n8n executions 25090, 25091, 25095 and 25096.

Those are four measurements on one instance, on one day, against one Anthropic account. They are not a benchmark and they are not a promise about your stack. Latency here is dominated by the model call, so it will move with the model you choose, your region, and how busy the provider is at that moment. The Postgres writes are not the expensive part.

Which build gotchas cost a failed run?

A data-modifying CTE is invisible to the rest of the same statement

This is the one worth the read, because it produced a bug that looked like a model failure and was not.

Log Lead Text runs a single SQL statement. A CTE inserts the homeowner's new message into demo_messages, and the final SELECT in that same statement builds the transcript by aggregating rows from demo_messages. The intuition is that the row inserted a moment ago is now there to be aggregated. It is not. In Postgres, every CTE in a statement sees the same snapshot of the table, taken when the statement started, so the row the CTE just inserted does not exist as far as the aggregation is concerned.

The visible symptom: the workflow qualified the lead against a transcript missing the customer's most recent message. It answered the message before the one the homeowner had just sent. Every log looked healthy. The model was behaving correctly on the input it was given, and the input was one message stale.

The fix is not a second CTE and not a smarter query. It is splitting the write from the read: append the new line to the aggregated transcript explicitly, with || E'\nCustomer: ' || $2 on the end. If you refactor that statement and drop the explicit append because it looks redundant, the bug comes straight back, and it will not announce itself.

The backend ships a finished slot label and the browser must not re-derive it

Parse Decision produces two things from the same Date object: slot_at, a UTC timestamp for the machine, and slot_label, a finished human string such as "Tomorrow, Sep 18 - 8:00am - 12:00pm". If a page instead formats slot_at itself in the browser, timezone conversion can shift the hour across the boundary the model chose, so an 8am to 12pm window renders as 12pm to 4pm and the chat bubble and the booking card visibly disagree about when someone is coming. Render slot_label as given. Never recompute it client-side.

Parse Decision salvages JSON out of a misbehaving model

The system prompt asks for bare JSON with no markdown fence. Nothing enforces that. The Code node tries a direct parse, falls back to extracting the first brace-delimited block out of prose, and finally falls back to a generic message with book: false rather than throwing. The homeowner is holding a phone waiting for a reply; a thrown error in that path is a dead webhook response, which is a worse outcome than a generic answer.

The webhooks are public, so the inputs are clamped

There is no auth in front of any of the three endpoints. Every field is length-capped and control-character-stripped before it reaches Postgres or a model. That keeps abuse cheap rather than impossible. If you fork this for a real intake flow, add rate limiting before you add anything else.

How do I run the speed-to-lead workflow myself?

Import the free workflow JSON (MIT) from github.com/waseemnasir2k26/n8n-workflows/tree/main/workflows/02-speed-to-lead, run schema.sql against a Postgres database, create the two credentials, and POST to the lead webhook.

What you have to supply:

  1. A Postgres database, with schema.sql applied first. The workflow writes only to its own three tables.
  2. An Anthropic API key, as a header-auth credential named x-api-key. The anthropic-version header is already a literal parameter on both nodes and needs no credential.
  3. A demo page of your own, if you want to see it as a conversation rather than as JSON in a terminal.

On running costs, in general terms: there are exactly two metered calls per conversation, one short Anthropic call for the first text and one for the qualifying decision, both output-token capped. There is no media generation and no other external API. The n8n instance and the Postgres database are infrastructure you host. Your provider dashboard is the only honest source for what that costs you, so check it rather than trusting anybody's arithmetic, including mine.

Frequently asked questions

How fast can an AI reply to a web form lead?

In this workflow, submit to first reply was 2.4 seconds and 1.9 seconds, measured on 2026-09-17 across n8n executions 25090, 25091, 25095 and 25096. Latency is dominated by the model call rather than by the database, so your figures will move with the model, the region and the provider load.

Is Lone Star Air a real company?

No. Lone Star Air is a fictional company invented for this demo, and every page of the public demo is labelled SIMULATED. No SMS provider is wired in: the text message is a JSON string returned to the caller for a page to render as a chat bubble. Treat the whole thing as a simulation.

Why does the workflow use two different Claude models?

Because the two jobs are not the same job. The first text is short, formulaic and latency-critical, so it runs on Anthropic Claude Haiku 4.5 capped at 200 output tokens. Qualifying a reply and choosing a visit window is a judgement call, so it runs on Claude Opus 5 capped at 300 output tokens.

Why did the workflow answer the wrong message?

Because a data-modifying CTE is invisible to the rest of the same SQL statement. The CTE inserted the newest message while the aggregation in the same statement still saw the pre-statement snapshot, so the transcript was one message stale. The fix is to append the new line explicitly rather than relying on the insert being visible.

What do I need to run this n8n workflow?

Two credentials and one database: a Postgres credential with schema.sql already applied, and a header-auth credential carrying your Anthropic API key as x-api-key. Both ship as REPLACE_ME in the public JSON. The demo page is not included and you build your own against the three webhook endpoints.

The rest of the series

One workflow per video, the JSON given away under MIT each time, built by Waseem Nasir at SkynetLabs. The other three published episodes:

WhatsApp +92 300 1001957 · Waseem Nasir, SkynetLabs

Hire SkynetLabs, our Top Rated agency on Fiverr: https://www.fiverr.com/agencies/skynetjoellc