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.

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.
JAVASCRIPTimport 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
- Create a new file at
app/api/webhooks/custom/route.js. - Implement a
POSThandler that reads thex-custom-secretheader. - Compare the header value against an environment variable
WEBHOOK_SECRET(see Using Environment Variables for setup). - 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 callreq.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.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

Headless WordPress + Next.js Frontend Development
Keep WordPress for content, get a lightning-fast Next.js frontend. The best of both worlds — familiar editing, modern speed.