Building autonomous AI agents with Next.js and n8n
Step‑by‑step guide to connect a Next.js UI to n8n workflows, creating self‑operating AI agents with low‑code automation.

Overview
An autonomous AI agent consists of three moving parts:
- Front‑end – a Next.js app that gathers user intent and displays results.
- Orchestrator – n8n workflows that decide which model to call, how to chain calls, and when to store data.
- Execution environment – Vercel (or any Node host) for the UI, n8n Cloud (or self‑hosted) for the workflows.
The guide builds a simple “Task‑assistant” that can:
- Receive a natural‑language request (e.g., “Schedule a meeting with John tomorrow at 3 pm”).
- Choose the appropriate OpenAI function call (calendar, email, reminder).
- Trigger the chosen action via n8n.
- Return a confirmation to the user.
All code is TypeScript, and the n8n side uses low‑code nodes only.
Directory layout
/my-agent/
│
├─ /pages/
│ ├─ index.tsx # UI entry point
│ └─ api/
│ └─ agent.ts # Proxy to n8n webhook
│
├─ /components/
│ └─ ChatBox.tsx # Minimal chat UI
│
├─ /lib/
│ └─ n8nClient.ts # Helper for signed webhook calls
│
└─ next.config.js # Vercel config (optional)
1. Create the n8n workflow
1.1 Webhook trigger
- Add a Webhook node.
- Method:
POST - Path:
/webhook/agent - Enable Response → Return data (so Next.js can read the result).
- Method:
1.2 Parse user request
Add a Function node called ParseIntent:
// Input: { text: string }
const { text } = items[0].json;
// Simple regex‑based intent detection (replace with LLM if needed)
let intent = 'unknown';
if (/schedule|meeting/i.test(text)) intent = 'calendar';
if (/email|send/i.test(text)) intent = 'email';
if (/remind|reminder/i.test(text)) intent = 'reminder';
return [{ json: { intent, original: text } }];1.3 Branch by intent
Add an IF node that checks {{ $json.intent }} === 'calendar', === 'email', === 'reminder'. Connect each branch to the appropriate action node.
1.4 Calendar action (example)
- HTTP Request – call Google Calendar API (or any internal endpoint).
- Set node – format the response:
{ status: 'scheduled', details: … }.
1.5 Email action
- SMTP node – send an email.
- Set node –
{ status: 'sent', to: … }.
1.6 Reminder action
- Delay node – wait for the requested time.
- Set node –
{ status: 'reminded', at: … }.
1.7 Return to caller
All branches converge on a final Respond node:
{
"status": "ok",
"result": {{$json}}
}Publish the workflow and copy the Webhook URL. It will look like:
https://your-n8n-instance.com/webhook/agent
2. Secure the webhook
n8n supports Signature Authentication. Generate a secret (e.g., AGENT_WEBHOOK_SECRET) and store it in Vercel environment variables.
In the Webhook node:
- Enable Authentication → Signature.
- Set Signature Header to
x-n8n-signature. - Choose HMAC SHA256 and paste the secret.
Now the webhook will reject any request that does not provide a matching HMAC.
3. Front‑end – Next.js UI
3.1 pages/index.tsx
import { useState } from 'react';
import ChatBox from '../components/ChatBox';
export default function Home() {
const [messages, setMessages] = useState<
{ role: 'user' | 'assistant'; text: string }[]
>([]);
const handleSubmit = async (input: string) => {
// Optimistic UI
setMessages((prev) => [...prev, { role: 'user', text: input }]);
const res = await fetch('/api/agent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: input }),
});
const data = await res.json();
setMessages((prev) => [
...prev,
{ role: 'assistant', text: data.result?.status ?? 'unknown' },
]);
};
return (
<main style={{ maxWidth: '600px', margin: 'auto', padding: '2rem' }}>
<h1>Task Assistant</h1>
<ChatBox messages={messages} onSubmit={handleSubmit} />
</main>
);
}3.2 components/ChatBox.tsx
import { useState, FormEvent } from 'react';
type Props = {
messages: { role: 'user' | 'assistant'; text: string }[];
onSubmit: (input: string) => Promise<void>;
};
export default function ChatBox({ messages, onSubmit }: Props) {
const [input, setInput] = useState('');
const handle = async (e: FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
await onSubmit(input);
setInput('');
};
return (
<div>
<div style={{ minHeight: '300px', marginBottom: '1rem' }}>
{messages.map((msg, i) => (
<div key={i} style={{ textAlign: msg.role === 'user' ? 'right' : 'left' }}>
<p>{msg.text}</p>
</div>
))}
</div>
<form onSubmit={handle}>
<input
type="text"
placeholder="Ask me to schedule, email, or remind"
value={input}
onChange={(e) => setInput(e.target.value)}
style={{ width: '80%' }}
/>
<button type="submit" style={{ width: '18%' }}>
Send
</button>
</form>
</div>
);
}3.3 API proxy – pages/api/agent.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { signWebhook } from '../../lib/n8nClient';
const WEBHOOK_URL = process.env.N8N_WEBHOOK_URL!;
const WEBHOOK_SECRET = process.env.N8N_WEBHOOK_SECRET!;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
res.setHeader('Allow', ['POST']);
return res.status(405).end('Method Not Allowed');
}
// Forward the payload to n8n
const payload = req.body;
const signature = signWebhook(JSON.stringify(payload), WEBHOOK_SECRET);
try {
const n8nRes = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-n8n-signature': signature,
},
body: JSON.stringify(payload),
});
const data = await n8nRes.json();
return res.status(200).json(data);
} catch (err) {
console.error('n8n call failed', err);
return res.status(502).json({ error: 'Workflow execution failed' });
}
}3.4 HMAC helper – lib/n8nClient.ts
import crypto from 'crypto';
export function signWebhook(body: string, secret: string): string {
return crypto.createHmac('sha256', secret).update(body).digest('hex');
}4. Environment variables
Add these to Vercel (or your .env.local for local dev):
N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/agent
N8N_WEBHOOK_SECRET=super-secret-123
5. Deployment checklist
| Step | Action | Typical failure |
|---|---|---|
| 1 | Push repo to GitHub | CI pipeline misconfiguration |
| 2 | Connect repo to Vercel | Missing env vars cause 502 on first request |
| 3 | Deploy n8n workflow | Wrong webhook path → 404 |
| 4 | Test end‑to‑end | Signature mismatch → 401 from n8n |
| 5 | Enable Vercel Edge Functions (optional) | Edge runtime does not support crypto without polyfill |
Tip: Enable Vercel’s Preview Deployments for each PR. The preview URL can be used as a temporary webhook target while you iterate on the workflow.
6. Extending the agent
| Feature | Where to add | Example |
|---|---|---|
| LLM intent extraction | Replace ParseIntent Function node with an OpenAI node. | Prompt: “Classify the following request into calendar, email, or reminder.” |
| Persistent memory | Add a Postgres node before the IF to store {{ $json.original }} and retrieve prior context. | Use SELECT … FROM conversations WHERE user_id = … |
| Multi‑step plans | Chain multiple HTTP Request nodes under the same intent branch. | For “Book a flight and send confirmation”, call flight API then email node. |
| Rate limiting | Insert a Throttle node after the webhook. | Max 5 calls per minute per user. |
7. Common pitfalls and how to address them
- Signature mismatch – Ensure the same secret is used in both n8n webhook config and the
signWebhookhelper. A stray newline in the secret string will break the HMAC. - CORS errors – The API route runs on the same domain as the UI, so CORS is not an issue. If you expose the webhook directly to the browser, add CORS headers in the n8n Webhook node.
- Timeouts – n8n default execution timeout is 30 seconds. Long‑running actions (e.g., large file uploads) need the Execute Workflow node with increased timeout or a background queue.
- Missing environment variables on local dev – Vercel CLI (
vercel dev) reads.env.local. Verify the variables are present before runningnpm run dev.
8. Recap
- Set up an n8n workflow that receives a webhook, decides on an intent, runs the appropriate low‑code node, and returns a JSON payload.
- Secure the webhook with HMAC and store the secret in Vercel.
- Build a minimal Next.js UI that posts user text to an internal API route, which forwards the request to n8n and returns the result.
- Deploy both sides, test the end‑to‑end flow, and iterate with additional LLM calls or persistence as needed.
The pattern scales: you can add more intents, replace the regex parser with a sophisticated model, or move the workflow to a self‑hosted n8n instance for tighter control. The core idea—using Next.js as a thin front‑end and n8n as the orchestrator—keeps the codebase small while giving you the flexibility of a full workflow engine.