Skip to main content
You want to enrich inbound leads automatically. A signup form gives you a LinkedIn URL, and a minute later a contact record with company, location and follower count, plus the job title when LinkedIn still exposes it, lands in your CRM. No manual copy-paste, no Zapier-style glue code. In this tutorial we’ll build that pipeline end-to-end. You’ll deploy a tiny Next.js webhook handler to Vercel, trigger the Bright Data LinkedIn Scraper API against a handful of profile URLs and watch mapped contact records appear in your Vercel logs within a minute. We stop at the mapped object. Sending it to a specific CRM is a one-line fetch call we’ll sketch out at the end.

What you’ll build

A Next.js API route deployed on Vercel that:
  1. Accepts a POST from Bright Data containing a JSON array of scraped LinkedIn profiles
  2. Maps each profile into a normalized CRM-shaped contact record
  3. Logs the mapped records so you can inspect them in the Vercel dashboard
You’ll then trigger a scrape from your terminal pointed at your deployed Vercel URL, and see two profiles get mapped and logged end-to-end. Estimated time: 25 minutes.

Prerequisites

Part 1: Scaffold the Next.js project

In a new terminal, create a minimal Next.js project:
Accept all defaults. You’ll get a working Next.js 14+ project with the App Router. Then move into it:

Part 2: Add the webhook route

Create the file app/api/webhook/linkedin/route.ts:
app/api/webhook/linkedin/route.ts
That’s the whole receiver. LinkedIn has restricted the job title field since Nov. 13, 2025, so 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:
The first run walks you through login and project creation. Accept the defaults. After a minute you should see output ending with: Example output. Your values will differ.
Copy that URL. Your webhook endpoint is:
Open your project in the Vercel dashboard and keep the Logs tab visible. That’s where your console.log output will appear when Bright Data POSTs to the endpoint.

Part 4: Trigger the scrape

Open a second terminal and trigger the Bright Data LinkedIn scraper, pointing it at your Vercel URL:
Replace 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.
The scrape runs asynchronously. You don’t need to do anything with the 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.
Notice that both profiles arrive in a single POST. Bright Data delivers the whole snapshot in one request, not one profile at a time.

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
Redeploy:
Re-run the same curl command from Part 4. This time your Vercel logs should show two mapped contacts. job_title is null because LinkedIn restricts the job title field: Example output. Your values will differ.
Each object in that log is a complete CRM-ready contact. Sending it to your CRM is a single fetch call. The commented-out block in the handler sketches the HubSpot version. Swap the URL and header for Salesforce, Pipedrive or whatever you use.

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 an auth_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:
Then verify that header in the handler before processing:
Set 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 the snapshot_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 endpoint URL: it must be your deployed Vercel URL followed by /api/webhook/linkedin, starting with https://
  • The auth_header value: 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 prints no 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?

The Authorization header Bright Data sent does not match Bearer ${process.env.WEBHOOK_SECRET}. Check these three things:
  • The auth_header value in the trigger URL is Bearer+ followed by the same secret you set as WEBHOOK_SECRET in Vercel. The + is a URL-encoded space
  • The trigger URL uses auth_header. The API ignores the older webhook_header_Authorization name and sends no Authorization header
  • You redeployed after you set WEBHOOK_SECRET in the Vercel project settings
For every Scraper API error message, see Scraper API error codes. For webhook payload size, response deadline and job limits, see Scraper API limits.

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
The final hop, POSTing each mapped contact to HubSpot, Salesforce or your own CRM, is a single fetch call that the commented-out block in 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.