Connect n8n to Your Next.js App: Webhooks, API Routes, and Real-Time Automations

February 22, 202611 min read

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.
Next.js App
  • API Routes
  • Server Actions
  • Auth
Events (POST)
user.created, payment.failed
API Calls (GET/POST)
/api/users, /api/actions
n8n
  • Workflows
  • AI Agents
  • Scheduled Tasks
The two-way communication between your Next.js app and n8n via webhooks and API routes.

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-def

For 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

1
n8n sends POST with payload + x-n8n-signature header
2
Your API route computes HMAC-SHA256(payload, secret)
3
Compare computed digest with received signature
✓ Match → Process request
✗ Mismatch → 401 Reject
How HMAC signature verification protects your webhook endpoints from unauthorized requests.

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-here

Part 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 events

This 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

Next.js App
API Routes • Server Actions • Auth
events / calls
n8n
Workflows • AI Agents • Schedules
Database
Supabase / PostgreSQL
External Services
Slack • Email • Stripe • CRM
Full production architecture: Next.js handles the product, n8n handles the automations around it.

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.

Found this article helpful? Support me to keep creating content like this!


More blog posts