Connect n8n to Your Next.js App: Webhooks, API Routes, and Real-Time Automations
You've got a Next.js app. You've got n8n running (self-hosted or on n8n Cloud). Now you need to connect them so your app can trigger workflows and your workflows can call back into your app.
This is the guide I wish I had when I started. No fluff - just the patterns that work in production.
The Two-Way Connection
n8n and your Next.js app communicate in two directions:
- App → n8n: Your app sends events to n8n via HTTP requests to n8n webhook URLs. Example: user signs up → trigger onboarding workflow.
- n8n → App: n8n calls your Next.js API routes to read data or trigger actions. Example: n8n checks if a user completed onboarding → calls your API to get user status.
- ▶ API Routes
- ▶ Server Actions
- ▶ Auth
- ▶ Workflows
- ▶ AI Agents
- ▶ Scheduled Tasks
Part 1: App → n8n (Triggering Workflows)
Step 1: Create a Webhook Trigger in n8n
In n8n, create a new workflow and add a Webhook node as the trigger. Set the HTTP method to POST. n8n gives you a URL like:
https://your-n8n.example.com/webhook/abc-123-defFor production, use the Production URL (not the test URL). The production URL works even when the n8n editor is closed.
Step 2: Create a Utility Function in Next.js
Create a reusable function to send events to n8n:
// lib/n8n.ts
const N8N_BASE_URL = process.env.N8N_WEBHOOK_BASE_URL!;
const N8N_API_KEY = process.env.N8N_API_KEY;
interface N8nEvent {
event: string;
data: Record<string, unknown>;
timestamp?: string;
}
export async function triggerN8nWorkflow(
webhookPath: string,
payload: N8nEvent
) {
const url = `${N8N_BASE_URL}/webhook/${webhookPath}`;
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(N8N_API_KEY && {
'Authorization': `Bearer ${N8N_API_KEY}`
}),
},
body: JSON.stringify({
...payload,
timestamp: payload.timestamp || new Date().toISOString(),
}),
});
if (!response.ok) {
console.error(`n8n webhook failed: ${response.status}`);
return false;
}
return true;
} catch (error) {
// Don't let n8n failures break your app
console.error('n8n webhook error:', error);
return false;
}
}Key detail: the try/catch ensures n8n failures never break your app. If n8n is down, the user experience is unaffected - the automation just doesn't fire.
Step 3: Fire Events from Your App
Call the utility wherever important events happen:
// app/api/auth/signup/route.ts
import { triggerN8nWorkflow } from '@/lib/n8n';
export async function POST(req: Request) {
const { email, name } = await req.json();
// 1. Create the user in your database
const user = await createUser({ email, name });
// 2. Fire the n8n webhook (non-blocking)
triggerN8nWorkflow('user-signup', {
event: 'user.created',
data: {
userId: user.id,
email: user.email,
name: user.name,
plan: 'free',
}
});
// 3. Return immediately - don't wait for n8n
return Response.json({ user });
}Notice we don't await the webhook call. The user shouldn't wait for your automation to complete before seeing the signup confirmation.
Part 2: n8n → App (Reading Data and Triggering Actions)
Step 1: Create an Internal API Route
Create API routes that n8n can call to read data or trigger actions in your app:
// app/api/internal/users/[userId]/route.ts
import { NextRequest, NextResponse } from 'next/server';
// Protect internal routes with a shared secret
function validateInternalAuth(req: NextRequest): boolean {
const authHeader = req.headers.get('authorization');
return authHeader === `Bearer ${process.env.INTERNAL_API_KEY}`;
}
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ userId: string }> }
) {
if (!validateInternalAuth(req)) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const { userId } = await params;
const user = await getUserById(userId);
if (!user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}
return NextResponse.json({
id: user.id,
email: user.email,
plan: user.plan,
onboardingComplete: user.onboardingComplete,
lastLoginAt: user.lastLoginAt,
});
}Step 2: Call Your API from n8n
In n8n, use an HTTP Request node to call your API route. Set the URL to your Next.js app's API endpoint and add the authorization header with your internal API key.
Example flow: n8n's onboarding workflow waits 3 days, then calls your API to check if the user completed onboarding. If not, it sends a nudge email.
Part 3: Securing the Connection
Webhook Signatures
For critical webhooks (especially payment-related), verify that requests actually come from n8n:
// lib/n8n-verify.ts
import { createHmac } from 'crypto';
export function verifyN8nWebhook(
payload: string,
signature: string,
secret: string
): boolean {
const hmac = createHmac('sha256', secret);
const digest = hmac.update(payload).digest('hex');
return signature === digest;
}
// Usage in your API route
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get('x-n8n-signature') || '';
if (!verifyN8nWebhook(body, signature, process.env.N8N_WEBHOOK_SECRET!)) {
return Response.json({ error: 'Invalid signature' }, { status: 401 });
}
const data = JSON.parse(body);
// Process the verified webhook...
}HMAC Signature Verification Flow
x-n8n-signature headerHMAC-SHA256(payload, secret)Environment Variables
Your .env.local needs these variables:
# n8n connection
N8N_WEBHOOK_BASE_URL=https://your-n8n.example.com
N8N_API_KEY=your-n8n-api-key
N8N_WEBHOOK_SECRET=shared-secret-for-hmac
# Internal API (for n8n to call your app)
INTERNAL_API_KEY=random-secure-string-herePart 4: Production Patterns
Queue Critical Events
For important events (payment, user deletion), don't fire and forget. Store them in your database and let n8n poll:
// Instead of direct webhook calls for critical events:
await db.events.create({
type: 'payment.failed',
data: { userId, invoiceId, amount },
processed: false,
createdAt: new Date(),
});
// n8n polls this table every minute via HTTP Request node
// and processes unprocessed eventsThis ensures no events are lost even if n8n is temporarily down.
Use Separate Webhooks per Event Type
Don't funnel everything through one webhook. Create separate n8n workflows for each event type:
/webhook/user-signup→ onboarding workflow/webhook/payment-failed→ dunning workflow/webhook/error-report→ error monitoring workflow/webhook/feedback-submitted→ feedback routing workflow
This keeps each workflow focused and easy to debug. If your payment recovery workflow breaks, it doesn't affect user onboarding.
Health Check Workflow
Build a simple n8n workflow that pings your app's health endpoint every 5 minutes and alerts you on Slack if it's down:
// app/api/health/route.ts
export async function GET() {
// Check critical services
const dbOk = await checkDatabase();
const cacheOk = await checkRedis();
const healthy = dbOk && cacheOk;
return Response.json(
{ status: healthy ? 'ok' : 'degraded', db: dbOk, cache: cacheOk },
{ status: healthy ? 200 : 503 }
);
}In n8n: Schedule Trigger (every 5 min) → HTTP Request (your health endpoint) → IF (status !== 200) → Slack notification. Free uptime monitoring.
Putting It All Together
Here's what your architecture looks like with n8n in the mix:
Production Architecture
Your Next.js app handles the core product. n8n handles everything that happens around it - notifications, integrations, monitoring, and AI-powered automation. Clean separation of concerns, and you can iterate on automations without touching your codebase.
New to n8n? Start with why developers are switching from Zapier or check out the 7 workflows every SaaS needs.
More blog posts