Back to Blog
Lesson 51 of the Next.js: Build Full-Stack Apps with the App Router course
Next.jsSeptember 7, 20264 min read

Webhooks and Integration: Securely Handling External Events

Learn to handle webhooks in Next.js. Master signature verification and trigger Server Actions to keep your application in sync with third-party services.

Next.jsWebhooksAPIIntegrationBackend
Close-up of a rusty chain securing a chrome handle on an old yellow metal door.

Previously in this course, we learned how to use Route Handlers to build custom APIs. In this lesson, we take that knowledge a step further by learning how to listen for and process webhooks.

A webhook is essentially a "reverse API." Instead of your application polling a service for updates, the external service pushes data to a specific URL in your app the moment an event occurs. Whether it's a payment confirmation from Stripe or a content update from a headless CMS, webhooks are the standard for real-time integration.

The Anatomy of a Webhook Integration

When you integrate with a service, you provide them with a URL (the "webhook endpoint"). When an event happens, they send an HTTP POST request to that URL with a JSON payload.

Because these requests come from the public internet, you must never trust them blindly. Every reputable service provides a "signature" in the request headers. You use a secret key to verify this signature, ensuring the request actually came from the intended provider and not an attacker.

Step 1: Creating the Webhook Route Handler

In Next.js, we create a route.js file inside the app/api/webhooks/route.js directory to act as our listener.

JAVASCRIPT
// app/api/webhooks/route.js
import { NextResponse } from CE9178">'next/server';

export async function POST(req) {
  const body = await req.text(); // Read as raw text for signature verification
  const signature = req.headers.get(CE9178">'x-signature');

  // We will add verification logic here
  
  const event = JSON.parse(body);
  console.log(CE9178">'Received event:', event);

  return NextResponse.json({ received: true }, { status: 200 });
}

Step 2: Verifying Signatures

Verification prevents "spoofing." Most services use HMAC (Hash-based Message Authentication Code). You take the raw request body and a shared secret key to generate a hash; if it matches the header signature, the request is authentic.

JAVASCRIPT
import crypto from CE9178">'crypto';

function verifySignature(payload, signature, secret) {
  const hmac = crypto.createHmac(CE9178">'sha256', secret);
  const digest = hmac.update(payload).digest(CE9178">'hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}

Step 3: Executing Server Actions

Once verified, you should trigger your business logic. While you can perform database operations directly in the route handler, it is cleaner to call a Server Action to keep your logic centralized.

JAVASCRIPT
// app/actions/webhooks.js
CE9178">'use server'

import { db } from CE9178">'@/lib/db';

export async function handlePaymentSuccess(data) {
  await db.order.update({
    where: { id: data.orderId },
    data: { status: CE9178">'PAID' }
  });
}

Hands-on Exercise

  1. Create a new file at app/api/webhooks/custom/route.js.
  2. Implement a POST handler that reads the x-custom-secret header.
  3. Compare the header value against an environment variable WEBHOOK_SECRET (see Using Environment Variables for setup).
  4. If they match, log "Webhook Verified" to your terminal; otherwise, return a 401 Unauthorized status.

Common Pitfalls

  • Reading the Body: Always read the request body as req.text() first. If you call req.json() before verifying the signature, the body parser may consume the stream, making it impossible to verify the hash correctly.
  • Timeouts: Webhooks expect a quick response (usually within a few seconds). If your processing logic takes a long time, perform the heavy lifting in a background queue or return a 200 OK immediately and process the data asynchronously.
  • Missing Idempotency: If a service retries a delivery due to a network blip, you might receive the same event twice. Ensure your database operations are idempotent—for example, check if an order is already marked as 'PAID' before attempting to update it again. For high-scale systems, consider handling webhooks with Redis to track processed IDs.

FAQ

Q: Do I need to use use client for webhook routes? A: No. Webhook routes are server-side API routes. They do not involve React components or the use client directive.

Q: Where do I store the secret key? A: Always use environment variables. Never hardcode secrets in your source code.

Q: How do I test webhooks locally? A: Use a tool like ngrok to expose your local development server to the internet, allowing external services to hit your localhost.

Recap

We've moved beyond standard UI development into system integration. By listening for webhooks, verifying their origin through cryptographic signatures, and offloading work to Server Actions, you can create robust, production-ready applications that react to the outside world in real-time. For more on testing these integrations, check out these strategies for mocking external services.

Up next: We will discuss how to implement authentication to protect your private routes.

Similar Posts