Skip to main content
RapidDev - Software Development Agency
lovable-integrationsEdge Function Integration

How to Integrate Lovable with LinkedIn Ads

To integrate Lovable with LinkedIn Ads, create a Supabase Edge Function that proxies LinkedIn Marketing API calls using OAuth2 credentials stored in Cloud → Secrets. The API provides campaign management, creative tracking, conversion data, and lead gen form sync. Use Lovable's chat to build B2B advertising dashboards, ABM campaign trackers, and LinkedIn lead generation pipelines.

What you'll learn

  • How to create a LinkedIn developer application and obtain Marketing API OAuth credentials
  • How to store LinkedIn OAuth tokens securely in Cloud → Secrets
  • How to build a Supabase Edge Function that proxies LinkedIn Marketing API calls
  • How to build B2B ad campaign dashboards with conversion tracking and lead gen form sync
  • How LinkedIn's B2B professional targeting differs from consumer-focused ad platforms
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read50 minutesSocialMarch 2026RapidDev Engineering Team
TL;DR

To integrate Lovable with LinkedIn Ads, create a Supabase Edge Function that proxies LinkedIn Marketing API calls using OAuth2 credentials stored in Cloud → Secrets. The API provides campaign management, creative tracking, conversion data, and lead gen form sync. Use Lovable's chat to build B2B advertising dashboards, ABM campaign trackers, and LinkedIn lead generation pipelines.

Build B2B advertising dashboards and lead generation pipelines with LinkedIn Marketing API

LinkedIn Marketing API is the programmatic interface for LinkedIn's advertising platform, which is the dominant channel for B2B advertising online. The API gives developers access to campaign management (create, update, pause campaigns), analytics (impressions, clicks, conversions, spend by targeting segment), creative management, and — critically for B2B marketers — lead gen form data (the leads captured through LinkedIn's native lead generation ad format).

The B2B targeting capabilities are LinkedIn's key differentiator: you can target by job title, seniority, function, company name, company size, industry, and years of experience. This level of professional targeting does not exist on any other major ad platform. For Account-Based Marketing (ABM) campaigns — targeting specific companies — LinkedIn's company targeting and Matched Audiences features provide capabilities that consumer ad platforms cannot replicate.

Building in Lovable, the most common use cases are: campaign performance dashboards for marketing teams, ABM account engagement trackers that show which target companies are interacting with ads, and lead gen form integrations that sync LinkedIn leads directly into Supabase (or further into CRM systems). The LinkedIn Marketing API requires a LinkedIn developer application with the Marketing Developer Platform product approved, which requires an application review process similar to other LinkedIn API products.

Integration method

Edge Function Integration

LinkedIn's Marketing API uses OAuth2 for authentication and provides REST endpoints for campaign management, analytics, and lead gen data. OAuth access tokens must stay server-side to prevent unauthorized access to advertising accounts and lead data. All LinkedIn Marketing API calls are proxied through a Supabase Edge Function using credentials stored in Cloud → Secrets.

Prerequisites

  • A Lovable account with a project that has Lovable Cloud enabled
  • A LinkedIn Ads account with at least one campaign or a test account with budget
  • A LinkedIn developer application approved for the Marketing Developer Platform product — apply at developer.linkedin.com
  • OAuth2 client ID and client secret from the LinkedIn developer portal
  • An OAuth2 access token with r_ads and r_ads_reporting scopes for your ad account

Step-by-step guide

1

Create a LinkedIn developer application and request Marketing API access

Navigate to developer.linkedin.com and sign in with your LinkedIn account. Click 'Create app'. Fill in the app name, your company's LinkedIn page (required for business applications), and app logo. After creating the app, go to the Products tab and click 'Request Access' next to 'Marketing Developer Platform'. LinkedIn's Marketing Developer Platform includes Campaign Manager, Reporting, Lead Gen Forms, and other marketing-specific products. LinkedIn reviews Marketing Developer Platform requests — this can take 2-7 business days. You will receive a notification when your application is approved. Until then, your application can use the API in a limited capacity but will receive permission errors on most Marketing API endpoints. Once approved, go to the Auth tab in your LinkedIn developer application. Note the Client ID and Client Secret. You will also need to configure the OAuth 2.0 redirect URLs — add your deployed Lovable app URL plus a callback path, such as https://your-app.lovable.app/auth/linkedin/callback. To generate an access token with the required r_ads and r_ads_reporting scopes, you use the OAuth2 authorization code flow. Go to the OAuth 2.0 tools section in the LinkedIn developer portal and use the token generator to create an access token for your LinkedIn account that includes the required scopes. This generates a token valid for 60 days. For production, you need to implement token refresh — LinkedIn provides refresh tokens that are valid for 365 days.

Pro tip: LinkedIn access tokens expire after 60 days. Store the refresh token alongside the access token and implement automatic token refresh in the Edge Function before the access token expires.

Expected result: Your LinkedIn developer application has Marketing Developer Platform access approved. You have a client ID, client secret, an access token (60-day validity), and a refresh token (365-day validity).

2

Store LinkedIn credentials in Cloud → Secrets

Open your Lovable project, click the '+' icon next to the Preview label, and navigate to the Secrets tab. Add the following secrets: - Name: LINKEDIN_ADS_ACCESS_TOKEN — Value: your 60-day access token - Name: LINKEDIN_ADS_REFRESH_TOKEN — Value: your 365-day refresh token - Name: LINKEDIN_ADS_CLIENT_ID — Value: your LinkedIn application Client ID - Name: LINKEDIN_ADS_CLIENT_SECRET — Value: your LinkedIn application Client Secret - Name: LINKEDIN_ADS_ACCOUNT_ID — Value: your LinkedIn ad account ID (the numeric ID from Campaign Manager's URL) The access token authorizes all advertising data retrieval and lead gen form access. The refresh token allows your Edge Function to automatically obtain a new access token when the current one expires, without user intervention. Your LinkedIn ad account ID is the numeric identifier in LinkedIn Campaign Manager's URL. When viewing your campaigns, the URL contains act=urn:li:sponsoredAccount:XXXXXXX — the number at the end is your account ID. Store it without the urn:li:sponsoredAccount: prefix — just the numeric portion. LinkedIn advertising credentials have access to billing data and lead contact information. Apply the same security discipline as for payment processor credentials: always use Cloud → Secrets, never paste in chat.

Expected result: All five LinkedIn Ads secrets appear in Cloud → Secrets with masked values.

3

Create the LinkedIn Marketing API proxy Edge Function

Create a Supabase Edge Function that proxies LinkedIn Marketing API calls. LinkedIn's Marketing API base URL is https://api.linkedin.com/v2/ for newer endpoints or https://api.linkedin.com/rest/ for the REST API. The function includes automatic token refresh logic — before making each API call, it checks if the access token has expired (by attempting the call and catching 401s) and refreshes it using the stored refresh token. The function handles four core actions: campaigns (list campaigns for the ad account), analytics (fetch performance metrics for campaigns, ad groups, or creatives), lead_forms (list lead gen forms and their submissions), and update_campaign (change campaign status or budget). LinkedIn's API uses the X-Restli-Protocol-Version: 2.0.0 header — include this on all requests. LinkedIn also encodes some resource identifiers as URNs (like urn:li:sponsoredCampaign:123456) — the Edge Function handles converting between numeric IDs and URN formats.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/linkedin-ads/index.ts. Accept POST requests with 'action' (campaigns | analytics | lead_forms | leads | update_campaign) and 'params'. Use LINKEDIN_ADS_ACCESS_TOKEN as the Authorization: Bearer header and X-Restli-Protocol-Version: 2.0.0. For campaigns: GET /rest/adCampaigns?q=search&search.account.values[0]=urn:li:sponsoredAccount:{LINKEDIN_ADS_ACCOUNT_ID}. For analytics: POST /rest/adAnalytics with the provided metrics and dateRange. For lead_forms: GET /rest/leadGenForms?q=owner&owner=urn:li:sponsoredAccount:{id}. Include CORS headers and handle 401 by refreshing the token.

Paste this in Lovable chat

supabase/functions/linkedin-ads/index.ts
1// supabase/functions/linkedin-ads/index.ts
2const LINKEDIN_API = 'https://api.linkedin.com';
3const TOKEN_URL = 'https://www.linkedin.com/oauth/v2/accessToken';
4
5async function refreshAccessToken(): Promise<string> {
6 const response = await fetch(TOKEN_URL, {
7 method: 'POST',
8 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
9 body: new URLSearchParams({
10 grant_type: 'refresh_token',
11 refresh_token: Deno.env.get('LINKEDIN_ADS_REFRESH_TOKEN')!,
12 client_id: Deno.env.get('LINKEDIN_ADS_CLIENT_ID')!,
13 client_secret: Deno.env.get('LINKEDIN_ADS_CLIENT_SECRET')!,
14 }),
15 });
16 const data = await response.json();
17 if (!data.access_token) throw new Error('Token refresh failed');
18 return data.access_token;
19}
20
21async function callLinkedIn(url: string, options: RequestInit, retried = false): Promise<Response> {
22 const token = Deno.env.get('LINKEDIN_ADS_ACCESS_TOKEN');
23 const response = await fetch(url, {
24 ...options,
25 headers: {
26 ...options.headers as Record<string, string>,
27 'Authorization': `Bearer ${token}`,
28 'X-Restli-Protocol-Version': '2.0.0',
29 },
30 });
31 if (response.status === 401 && !retried) {
32 await refreshAccessToken(); // In production: update the secret
33 return callLinkedIn(url, options, true);
34 }
35 return response;
36}
37
38Deno.serve(async (req) => {
39 if (req.method === 'OPTIONS') {
40 return new Response(null, {
41 headers: {
42 'Access-Control-Allow-Origin': '*',
43 'Access-Control-Allow-Methods': 'POST, OPTIONS',
44 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
45 },
46 });
47 }
48
49 const { action, params } = await req.json();
50 const accountId = Deno.env.get('LINKEDIN_ADS_ACCOUNT_ID');
51 const accountUrn = `urn:li:sponsoredAccount:${accountId}`;
52
53 let url: string;
54 let fetchOptions: RequestInit = { method: 'GET', headers: {} };
55
56 if (action === 'campaigns') {
57 url = `${LINKEDIN_API}/rest/adCampaigns?q=search&search.account.values[0]=${encodeURIComponent(accountUrn)}&count=100`;
58 } else if (action === 'analytics') {
59 url = `${LINKEDIN_API}/rest/adAnalytics?q=analytics&pivot=${params.pivot || 'CAMPAIGN'}&dateRange.start.day=${params.start_day}&dateRange.start.month=${params.start_month}&dateRange.start.year=${params.start_year}&dateRange.end.day=${params.end_day}&dateRange.end.month=${params.end_month}&dateRange.end.year=${params.end_year}&accounts[0]=${encodeURIComponent(accountUrn)}&fields=impressions,clicks,costInLocalCurrency,totalEngagements,leadGenerationMailContactInfoShares`;
60 } else if (action === 'lead_forms') {
61 url = `${LINKEDIN_API}/rest/leadGenForms?q=owner&owner=${encodeURIComponent(accountUrn)}`;
62 } else {
63 return new Response(JSON.stringify({ error: 'Invalid action' }), { status: 400, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } });
64 }
65
66 const response = await callLinkedIn(url, fetchOptions);
67 const data = await response.json();
68
69 return new Response(JSON.stringify(data), {
70 status: response.ok ? 200 : response.status,
71 headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
72 });
73});

Pro tip: LinkedIn's API uses URN-encoded resource identifiers. When you receive campaign IDs from the campaigns endpoint, they will be in URN format like urn:li:sponsoredCampaign:123456. Store the full URN when saving to Supabase and use it directly in subsequent API calls.

Expected result: The linkedin-ads Edge Function is deployed. Calling it with { action: 'campaigns' } returns LinkedIn campaign objects with their IDs, names, status, and budget information.

4

Build the B2B campaign and lead dashboard

With the Edge Function in place, use Lovable's chat to build the campaign analytics and lead generation dashboard. LinkedIn's advertising data is particularly valuable for B2B teams because of the available demographic breakdown — you can see impressions and clicks by job title, seniority, company, and industry. For the campaign dashboard, show the standard performance metrics (impressions, clicks, CTR, spend, leads, CPL) with a date range picker. LinkedIn's analytics endpoint supports pivoting by CAMPAIGN, CREATIVE, CONVERSION, MEMBER_COMPANY, MEMBER_JOB_TITLE, and MEMBER_SENIORITY — use the member-level pivots to power the ABM account engagement view. For the lead gen form integration, fetch lead form responses and display them in a table with the lead's first name, last name, email, company, and job title. Add a button to export leads as CSV or sync them to a Supabase table that feeds your CRM workflow. LinkedIn's API returns cost in local currency as costInLocalCurrency (a decimal). Divide by 100 if your account uses cents, or display it directly if it is already in dollar/euro units — LinkedIn's currency handling varies by account region. For enterprise-scale B2B dashboards with Salesforce CRM sync, Marketo lead routing, and multi-account agency reporting, RapidDev's team can help design the full data pipeline.

Lovable Prompt

Build a LinkedIn Ads dashboard with two tabs: (1) Campaign Performance showing a table of all campaigns with impressions, clicks, CTR, spend, leads, and CPL from the linkedin-ads Edge Function analytics action, with a date range picker; (2) Lead Generation showing incoming lead gen form submissions with name, company, job title, email, form name, and submission date, with a 'Export CSV' button and a 'Mark as Reviewed' button that updates a Supabase 'linkedin_leads' table.

Paste this in Lovable chat

Expected result: A LinkedIn Ads dashboard shows campaign performance metrics and lead gen form submissions. The campaign table refreshes when the date range changes. The leads table shows incoming LinkedIn leads that can be marked as reviewed or exported.

Common use cases

B2B campaign performance dashboard

Build a marketing dashboard that shows LinkedIn campaign performance across all active campaigns: spend, impressions, clicks, CTR, conversion events, and cost-per-lead. The dashboard lets the marketing team compare performance across campaigns, ad creatives, and targeting segments without needing LinkedIn Ads Manager access.

Lovable Prompt

Create a LinkedIn Ads dashboard that fetches all campaigns for my LinkedIn ad account from the linkedin-ads Edge Function. Show a table with campaign name, status, daily budget, spend MTD, impressions, clicks, CTR, leads, and CPL. Add a date range picker and a filter for campaign objective (lead generation, website visits, brand awareness). Include a totals row.

Copy this prompt to try it in Lovable

Lead gen form sync to Supabase CRM

Sync leads captured through LinkedIn's Lead Gen Forms directly into a Supabase table in real time. When someone submits a Lead Gen Form on a LinkedIn ad, the Edge Function fetches the new lead data from the LinkedIn API and inserts it into Supabase with the lead's name, company, job title, and email — triggering any downstream notifications or CRM sync workflows.

Lovable Prompt

Build a lead sync dashboard that calls the linkedin-ads Edge Function to fetch new lead gen form submissions from the last 24 hours. Display incoming leads in a table with name, company, title, email, campaign name, form name, and submission date. Add a 'Sync to CRM' button that marks leads as exported and saves them to a Supabase table called 'linkedin_leads'. Show a badge count of unsynced leads.

Copy this prompt to try it in Lovable

ABM account engagement tracker

Create an account-based marketing dashboard that shows which target companies are engaging with LinkedIn ads — impressions and clicks from employees at specific companies. The dashboard uses LinkedIn's demographic reporting to aggregate engagement by company, helping sales teams understand which accounts are warming up to outreach.

Lovable Prompt

Build an ABM tracker that calls the linkedin-ads Edge Function to fetch demographic breakdown reports segmented by company for my LinkedIn campaigns over the last 30 days. Display the top 25 companies by impression share with their impression count, click count, and CTR. Show a trend line for each company's engagement over time. Store results in Supabase for week-over-week comparison.

Copy this prompt to try it in Lovable

Troubleshooting

API returns 403 Forbidden with 'Not enough permissions to access'

Cause: The access token does not have the r_ads or r_ads_reporting scope, or the LinkedIn developer application has not been approved for the Marketing Developer Platform product.

Solution: Check the OAuth scopes on your access token by decoding the JWT at jwt.io — verify r_ads and r_ads_reporting are present. Then go to developer.linkedin.com, open your app, and check the Products tab to confirm the Marketing Developer Platform is listed as 'Approved'. If it shows 'In Review', your API calls will continue to fail until LinkedIn approves the application.

Analytics endpoint returns empty elements array even for active campaigns

Cause: The dateRange parameters in the analytics request are incorrectly formatted, or the campaigns were not active during the requested date range.

Solution: LinkedIn's analytics API requires separate day, month, and year integer fields for the date range, not ISO date strings. Verify the request includes dateRange.start.day, dateRange.start.month, and dateRange.start.year as integers. Months are 1-indexed (January = 1). Also confirm the campaigns you are querying were running during the requested date range — LinkedIn only returns data for periods when the campaign was active.

typescript
1// Correct LinkedIn Analytics date range format
2const startDate = new Date();
3startDate.setDate(startDate.getDate() - 30);
4const params = {
5 start_day: startDate.getDate(), // integer
6 start_month: startDate.getMonth() + 1, // integer, 1-indexed
7 start_year: startDate.getFullYear(), // integer
8 end_day: new Date().getDate(),
9 end_month: new Date().getMonth() + 1,
10 end_year: new Date().getFullYear(),
11};

Lead gen form responses endpoint returns 400 Bad Request

Cause: The lead gen forms endpoint requires the X-Restli-Protocol-Version: 2.0.0 header. Without it, the API may return 400 or 415 errors.

Solution: Confirm every fetch call in the Edge Function includes the X-Restli-Protocol-Version: 2.0.0 header. This is a LinkedIn API requirement across all REST endpoints. Add it to a shared headers object so it is never accidentally omitted.

Best practices

  • Store both the access token and refresh token in Cloud → Secrets so the Edge Function can automatically renew credentials when the 60-day access token expires
  • Always include the X-Restli-Protocol-Version: 2.0.0 header on every LinkedIn API request — omitting it causes inconsistent behavior and 400 errors on some endpoints
  • Use LinkedIn's demographic pivot options (MEMBER_COMPANY, MEMBER_JOB_TITLE, MEMBER_SENIORITY) in analytics requests to build ABM engagement reports showing which companies and job titles engage with your ads
  • Store lead gen form responses in Supabase immediately when fetched — LinkedIn's Lead Gen API only stores leads for 90 days before they are no longer accessible via API
  • Cache campaign list and analytics data in Supabase with a 15-minute TTL — LinkedIn's reporting API has latency and rate limits that make live queries per user interaction impractical
  • Display cost data using the account's actual currency — LinkedIn returns costInLocalCurrency which varies by account region and may require division by 100 for some account types
  • Keep the advertiser account ID in Cloud → Secrets rather than hardcoded, to make it easy to switch between sandbox and production accounts or manage multiple clients

Alternatives

Frequently asked questions

How long do LinkedIn Marketing API access tokens last?

LinkedIn access tokens for the Marketing API expire after 60 days. LinkedIn also issues a refresh token valid for 365 days alongside the access token. Store both in Cloud → Secrets and implement the token refresh logic in your Edge Function — when the access token expires, use the refresh token to request a new one from LinkedIn's token endpoint. If the refresh token also expires, you need to re-authorize the OAuth connection by going through the full authorization code flow again.

Can I access LinkedIn lead gen form submissions through the API?

Yes — the LinkedIn Marketing API provides a Lead Gen Forms endpoint that returns submissions from your lead generation ad campaigns. Lead data includes the information the user submitted (typically name, email, job title, company) based on the form fields you configured. Note that LinkedIn only stores lead gen form responses for 90 days via the API. For permanent storage, sync leads to Supabase immediately after they are captured using a scheduled Edge Function that polls for new submissions daily.

What does 'Marketing Developer Platform' approval require?

LinkedIn's Marketing Developer Platform product requires a developer application review, which typically takes 2-7 business days. During the review, LinkedIn assesses your intended use case, company, and compliance with their advertising policies. Approved use cases typically include marketing analytics tools, agency campaign management platforms, and CRM integrations. Consumer-facing ad networks or tools that aggregate LinkedIn data at scale may face additional scrutiny or rejection.

How do I target by specific company names in LinkedIn Ads via the API?

LinkedIn's Account-Based Marketing targeting uses the 'MATCHED_AUDIENCE' targeting facet. To target by company, you first upload a list of company names or domains via the Matched Audiences API, which creates an audience segment. Then reference that audience segment URN in your ad group targeting criteria. This is the same functionality available in LinkedIn Campaign Manager's 'Company List' targeting option, just accessed programmatically. Building this in Lovable involves creating an upload flow for the company list and then referencing the resulting audience URN when creating campaigns.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.