Building a Lead Capture API
Complete guide to building a production-ready lead capture system.
Overview
Build a complete lead capture system with form handling, validation, notifications, and CRM integration.
Architecture
[Landing Page] → [API Route] → [Validation] → [PropTechUSA] → [Notifications]
↓
[Rate Limit]
Step 1: Create the API Route
// app/api/leads/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { proptech } from '@/lib/proptech';
import { z } from 'zod';
const leadSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
phone: z.string().min(10),
address: z.string().optional(),
message: z.string().optional(),
});
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// Validate input
const data = leadSchema.parse(body);
// Create lead in PropTechUSA
const lead = await proptech.leads.create({
...data,
source: 'website',
metadata: {
ip: request.ip,
userAgent: request.headers.get('user-agent'),
referrer: request.headers.get('referer'),
},
});
return NextResponse.json({ success: true, id: lead.id });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Validation failed', details: error.errors },
{ status: 400 }
);
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
Step 2: Add Rate Limiting
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '1 m'), // 5 requests per minute
});
// In your API route:
const ip = request.ip ?? '127.0.0.1';
const { success } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
Step 3: Send Notifications
// After creating lead
await Promise.all([
// Slack notification
proptech.notify.slack({
channel: '#leads',
message: New lead: ${lead.name} (${lead.email}),
}),
// Email to team
proptech.notify.email({
to: 'team@yourdomain.com',
subject: 'New Lead Received',
leadId: lead.id,
}),
]);
Step 4: Frontend Form
'use client';
import { useState } from 'react';
export function LeadForm() {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setLoading(true);
const formData = new FormData(e.currentTarget);
const response = await fetch('/api/leads', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(formData)),
});
if (response.ok) {
setSuccess(true);
}
setLoading(false);
}
if (success) {
return <div>Thanks! We'll be in touch soon.</div>;
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<input name="phone" type="tel" placeholder="Phone" required />
<textarea name="message" placeholder="Message" />
<button type="submit" disabled={loading}>
{loading ? 'Submitting...' : 'Get My Cash Offer'}
</button>
</form>
);
}