What you’ll build
A Next.js API route deployed on Vercel that:- Accepts a POST from Bright Data containing a JSON array of scraped LinkedIn profiles
- Maps each profile into a normalized CRM-shaped contact record
- Logs the mapped records so you can inspect them in the Vercel dashboard
Prerequisites
- A Bright Data account with an API key (get your key)
- A free Vercel account
- Node.js 18+ installed locally
- The Vercel CLI installed:
npm install -g vercel
Part 1: Scaffold the Next.js project
In a new terminal, create a minimal Next.js project:Part 2: Add the webhook route
Create the fileapp/api/webhook/linkedin/route.ts:
app/api/webhook/linkedin/route.ts
position is usually empty and the route prints no title returned instead. Next.js App Router treats any route.ts file as an API endpoint, so this file alone gives you a working POST /api/webhook/linkedin route once deployed.
Part 3: Deploy to Vercel
From the project root:Part 4: Trigger the scrape
Open a second terminal and trigger the Bright Data LinkedIn scraper, pointing it at your Vercel URL:YOUR_API_KEY with your Bright Data API key and linkedin-to-crm-<hash> with your actual Vercel URL.
You should see a response like this immediately:
Example output. Your values will differ.
snapshot_id. Bright Data POSTs the results to your Vercel endpoint when the job finishes. For two profiles, that usually takes 30 to 60 seconds.
Switch to the Vercel Logs tab. Within a minute you should see something like:
Example output. Your values will differ.
Part 5: Map profiles to a CRM shape
Right now the handler just logs names and positions. Real CRMs expect contact records with specific field names:full_name, job_title, company, and so on. Let’s normalize the Bright Data payload into that shape.
Replace app/api/webhook/linkedin/route.ts with:
app/api/webhook/linkedin/route.ts
job_title is null because LinkedIn restricts the job title field:
Example output. Your values will differ.
Securing the webhook
Right now anyone who guesses your Vercel URL could POST fake profile data to it. Before you pipe live data into a real CRM, lock down the endpoint. Bright Data’s trigger call accepts anauth_header query parameter, which gets forwarded as the Authorization header on the webhook POST. The name webhook_header_Authorization, which this tutorial used before September 7, 2026, is ignored by the API:
WEBHOOK_SECRET as an environment variable in your Vercel project settings, then redeploy.
Bright Data also publishes a list of webhook source IPs you can allowlist. See Which IPs do webhooks come from? in the delivery guide.
Troubleshooting
Why does no webhook appear in the Vercel logs?
First confirm the scrape finished. Send thesnapshot_id from Part 4 to Monitor progress and wait for the status field to read ready. If the status is ready and the Vercel logs show no POST, check the delivery path:
- The
endpointURL: it must be your deployed Vercel URL followed by/api/webhook/linkedin, starting withhttps:// - The
auth_headervalue: it must match the secret the route checks. A mismatch makes the route return 401 before it logs anything - The response time: the route must return HTTP 200 within 30 seconds. Otherwise Bright Data treats the delivery as failed and retries it
- Vercel Deployment Protection: according to Vercel’s Deployment Protection docs, Standard Protection “protects all domains except production domains.” On a protected URL, Vercel asks for a Vercel login and the Bright Data POST never reaches the route. Check Settings > Deployment Protection in your Vercel project
Why are the job title and experience empty?
Empty job titles are expected. Since Nov. 13, 2025, LinkedIn has restricted public access to the Position, Experience and Education fields, and their fill rates dropped to roughly 2 percent. The route printsno title returned and the mapper sets job_title to null in that case. See LinkedIn data policy change for the full field status table.
Why does my route return a 401?
TheAuthorization header Bright Data sent does not match Bearer ${process.env.WEBHOOK_SECRET}. Check these three things:
- The
auth_headervalue in the trigger URL isBearer+followed by the same secret you set asWEBHOOK_SECRETin Vercel. The+is a URL-encoded space - The trigger URL uses
auth_header. The API ignores the olderwebhook_header_Authorizationname and sends noAuthorizationheader - You redeployed after you set
WEBHOOK_SECRETin the Vercel project settings
Congratulations
You’ve built an end-to-end pipeline that scrapes LinkedIn profiles and delivers CRM-ready contact records to your own server:- A Next.js API route deployed on Vercel that receives Bright Data webhooks
- A mapper function that normalizes Bright Data’s profile schema into a CRM-shaped contact
- A trigger call that fires the scrape asynchronously and tells Bright Data exactly where to send the results
route.ts sketches.
Next steps
Async batch requests
Scrape hundreds of URLs in a single batch job.
Amazon S3 delivery
Swap the webhook for an S3 bucket when payloads exceed Vercel’s 4.5 MB body limit.
Webhook reference
Full parameter list, auth headers and IP allowlist.
HubSpot Contacts API
Drop-in replacement for the commented POST in the handler.