How to Build a Custom Webhook Listener in Node.js / Next.js for Form Payloads
How to Build a Custom Webhook Listener in Node.js / Next.js for Form Payloads
When connecting web forms to your proprietary tech stack, third-party connectors like Zapier or Make aren't always enough.
You may need to:
- Provision a tenant database record in PostgreSQL via Drizzle or Prisma.
- Trigger a custom background job via Inngest or BullMQ.
- Send a personalized push notification to a mobile app via Firebase.
To do this, you need a dedicated Webhook Listener API Route.
Here is the complete, production-ready TypeScript guide to building a secure, performant webhook receiver in Next.js (App Router) to process form submissions.
The Complete Next.js Route Handler Implementation
Create the route handler at src/app/api/webhooks/infiform/route.ts:
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
// Secret key from your InfiForm Webhook settings dashboard
const WEBHOOK_SECRET = process.env.INFIFORM_WEBHOOK_SECRET || '';
export async function POST(req: NextRequest) {
try {
// 1. Read the raw text body (DO NOT parse as JSON yet!)
const rawBody = await req.text();
const signatureHeader = req.headers.get('infiform-signature');
if (!signatureHeader || !WEBHOOK_SECRET) {
return NextResponse.json({ error: 'Missing signature or secret' }, { status: 401 });
}
// 2. Cryptographically verify the HMAC SHA-256 signature
const isValid = verifySignature(rawBody, signatureHeader, WEBHOOK_SECRET);
if (!isValid) {
return NextResponse.json({ error: 'Invalid HMAC signature' }, { status: 403 });
}
// 3. Parse JSON safely once verified
const payload = JSON.parse(rawBody);
const { event, data, id } = payload;
// 4. Handle event types
if (event === 'submission.created') {
const { answers } = data;
console.log(`Received valid submission: ${id} from ${answers.workEmail}`);
// TODO: Call your database or queue here
// await db.insert(leads).values({ email: answers.workEmail });
}
// 5. Always return 200 OK quickly (< 500ms) to prevent sender retry timeouts
return NextResponse.json({ received: true });
} catch (error) {
console.error('Webhook processing failure:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
function verifySignature(rawBody: string, header: string, secret: string): boolean {
const parts = header.split(',');
const timestamp = parts.find(p => p.startsWith('t='))?.replace('t=', '');
const signature = parts.find(p => p.startsWith('v1='))?.replace('v1=', '');
if (!timestamp || !signature) return false;
// Reject replays older than 5 minutes (300 seconds)
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestamp, 10)) > 300) {
return false;
}
const computedSig = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computedSig));
}
3 Critical Pitfalls to Avoid in Next.js Webhooks
1. The req.json() Trap
Never call await req.json() before signature verification!
Calling req.json() deserializes the request body. If you attempt to re-stringify it with JSON.stringify(), key ordering and whitespace discrepancies will alter the string, causing cryptographic signature verification to fail.
Always read await req.text() first.
2. Blocking the Route Handler on Slow Database Tasks
If your route handler attempts to execute slow operations synchronously (like generating a heavy PDF report or calling five external APIs), the HTTP request will exceed the 3-second sender timeout, triggering automated retry spam.
Always return NextResponse.json({ received: true }) immediately, and dispatch heavy tasks to background workers (Inngest, SQS, or Redis).
3. Route Segment Config for Dynamic Execution
Ensure your Next.js route is configured as dynamic:
export const dynamic = 'force-dynamic';
Conclusion: Direct API Control
Building your own webhook listener gives you complete programmatic control over how customer data flows into your core infrastructure.
Deploy this route handler today, configure your webhook endpoint in InfiForm, and enjoy instant, secure, real-time data automation.
Related Articles
How to Embed Forms in Next.js and React Applications via Clean Script Embeds
Embedding third-party iframes in Next.js often causes hydration errors and scrollbar bugs. Here is the modern React component guide to embedding forms cleanly.
How to Connect Forms to n8n for Completely Free, Self-Hosted Lead Pipelines
n8n allows you to run complex multi-step automations without paying Zapier task fees. Here is how to build a high-volume, self-hosted lead routing pipeline.
Why Google Forms Lacks Webhook Retries (And How to Avoid Dropped Submissions)
When Google Forms pushes submissions to Zapier or Make, API timeouts silently drop customer data. Here is how modern webhook retry queues guarantee zero lost leads.