Lead Sync API Migration
LinkedIn Marketing Solutions is releasing a new Lead Sync API product. This migration guide facilitates the onboarding of existing developers to the new product and endpoints. If you fall into one or more of these categories, this migration guide is for you:
- Currently have access to Ads Lead Sync and or Events Lead Generation and syncs leads
- Currently have access to Advertising API and creates forms
NOTE: There are no schema changes for the Events Lead Generation API
Permissions for Lead Sync API
With the launch of the Lead Sync API, we’re introducing the following new permissions and endpoints to simplify syncing leads across different lead types:
Old API endpoint | Old Permission Name | Supported Lead Types | Last Supported Version | Last Supported Version will stop working on |
---|---|---|---|---|
/adFormQuestions | r_ads_leadgen_automation | SPONSORED | 202307 | December 16, 2024 |
/adForms | r_ads_leadgen_automation | SPONSORED | 202307 | December 16, 2024 |
/adFormResponses | r_ads_leadgen_automation | SPONSORED | 202307 | December 16, 2024 |
/adForms/Consents | r_ads_leadgen_automation | SPONSORED | 202307 | December 16, 2024 |
/leadNotificationUrls | r_ads_leadgen_automation | SPONSORED | 202307 | December 16, 2024 |
/leadGenFormQuestions | r_events_leadgen_automation | EVENT | 202307 | December 16, 2024 |
/leadGenForms | r_events_leadgen_automation | EVENT | 202307 | December 16, 2024 |
/eventFormResponses | r_events_leadgen_automation | EVENT | 202307 | December 16, 2024 |
/leadGenFormLegalInfo | r_events_leadgen_automation | EVENT | 202307 | December 16, 2024 |
/leadGenForms | r_events_leadgen_automation | EVENT | 202307 | December 16, 2024 |
/eventLeadNotifications | r_events_leadgen_automation | EVENT | 202307 | December 16, 2024 |
/leadGenFormVersions | r_events_leadgen_automation | EVENT | 202307 | December 16, 2024 |
/events | r_events_leadgen_automation | EVENT | 202306 | December 16, 2024 |
New API | New Permission Name | Supported Version starts from |
---|---|---|
/leadForms | r_marketing_leadgen_automation rw_ads r_ads |
202307 |
/leadFormResponses | r_marketing_leadgen_automation | 202307 |
/leadNotifications | r_marketing_leadgen_automation | 202307 |
/events | r_events | 202307 |
AdFormResponses API → LeadFormResponses API
Workflow Changes
- The new Lead Sync API supports fetching different types of leads based on the request (SPONSORED, EVENTS etc.)
- The new associatedEntity field represents which entity the lead is collected from e.g. for the Sponsored use case, associatedEntity is the corresponding SponsoredCreativeUrn ad for the Event use case, associatedEntity is the corresponding EventUrn.
- The /leadFormResponses API supports two different types of Owners. For the Sponsored use case, the owner is SponsoredAccountUrn. For Organic use cases like Event lead sync, the owner is OrganizationUrn.
Schema Changes
Schema
AdFormResponses API | LeadGenFormResponses API |
---|---|
id | id |
adFormUrn | versionedLeadGenFormUrn |
sponsoredAccountUrn | LeadGenFormOwner: SponsoredAccountUrn for Sponsored usecase, OrganizationUrn for other usecases. |
SponsoredCampaignUrn | SponsoredLeadMetadata: SponsoredCampaignUrn for Sponsored use case. Currently we don’t have other metadata information collected for non-sponsored usecase. |
SponsoredCreativeUrn | associatedEntity Union: - SponsoredCreativeUrn for Sponsored usecase - OrganizationUrn for Company Page usecase - EventUrn for Event usecase - StandardizedProductUrn for Product Page usecase - OrganizationLandingPageUrn for Landing Page usecase. |
leadType | leadType |
testLead | testLead |
submittedAt | submittedAt |
AdForms API→ LeadForms API
Workflow Changes
- The new API supports both sponsored (previously called adForms) and organic LeadGen Forms with a single integration.
- SponsoredAccount field is removed in favor of an Owner record which is a union of SponsoredAccountUrn and an OrganizationUrn.
- Sponsored forms will have a sponsored account as an owner whereas organic forms will have organization as the owner.
- Form fields now support multi locale strings. This means instead of providing just the string values marketers will need to provide locale and string value.
- e.g. question field of a form being created in English locale
Here"question": { "localized": { "en_US": "Last Name" } }
Last Name
is the question string value to be used foren_US
locale. - e.g. question field of a form being created in Spanish locale
Here"question": { "localized": { "es_ES": "Apellido" } }
Apellido
is the question string value to be used fores_ES
locale.
- e.g. question field of a form being created in English locale
- Finder API supports fetching forms under sponsored account or an organization (organic forms).
Schema Changes
Schema
AdForms API | LeadForms API | Notes |
---|---|---|
id | id & versionId | id is still the unique identifier of the form. Version Id is for managing form edits. |
locale | creationLocale | |
reviewInfo | reviewInfo | |
changeAuditStamps | Flattened and simplified to created and lastModified fields |
|
status | state | SUBMITTED maps to PUBLISHED DRAFT maps to DRAFT ARCHIVED maps to ARCHIVED |
form.hiddenFields | hiddenFields | |
account | owner. Union of SponsoredAccountUrn for Sponsored usecase, OrganizationUrn for other usecases. | |
form.name | name | |
form field | Renamed to content - headline & description are now multilocale stringsWithin form.questions :- question is now a multilocale string- typeSpecificQuestionDetails renamed to questionDetails - questionSubmissionCriteria is a new field to setup question submission criteria - label is a new field serves as a unique identifier within the form to specify the question |
|
form.consents , form.privacyPolicy , form.legalDisclaimer |
Moved under content.legalInfo - legalDisclaimer is now a multilocale string - privacyPolicy renamed to privacyPolicyUrl Within form.consents : - consentId renamed to id - consentRequired renamed to checkRequired - consent is now a multilocale string |
|
form.landingPage , form.thankYouMessage , form.thankYouPageCallToAction |
Moved under content.postSubmissionInfo - thankYouMessage is now a multilocale string |
LeadNotificationUrls → LeadNotifications API
Workflow Changes
- LeadNotifications API supports storing notification URLs (webhooks) disassociated with ad entities (i.e. old API requires an account/campaign/creative to be tied to the notification object).
- Support storing notification URLs at varying levels of granularity, most notably at the form level.
- Support storing URLs for the same form/owner under different lead types, such as SPONSORED and EVENT.
- Support push notifications for lead submissions as well as deletions.
Schema Changes
Schema
LeadNotificationUrls API | LeadNotifications API | Notes |
---|---|---|
key | Instead of a key, please use the following fields to specify the level of granularity that notifications should be sent: - owner - leadType - versionedFormUrn - associatedEntity |
For example, to subscribe to notifcations at the sponsored account level, set the owner field to your specific SponsoredAccountUrn and set leadType to SPONSORED. |
status | Removed. | All subscriptions are active by default. To stop receiving a notifications under a subscription, please delete it. |
URL | webhook |
Notification Payload
LeadNotificationUrls API | LeadNotifications API | Notes |
---|---|---|
createdTime | occuredAt | |
accountUrn | owner | For the SPONSORED lead type the owner is a sponsored account urn. For all other lead types the owner is an organization urn. |
campaignUrn | Removed. | Only sponsored leads will have a campaignUrn, in this case the campaign and related sponsored metadata can be obtained by fetching the lead directly. |
creativeUrn | associatedEntity | For the SPONSORED lead type the creative urn will be returned via the associatedEntity field. For other lead types this field may resolve to other urns. |
formUrn | leadGenForm | This will be a VersionedLeadGenFormUrn instead of an AdFormUrn . |
adFormResponseUrn | leadGenFormResponse | This will be a LeadGenFormResponseUrn instead of an AdFormResponseUrn |
N/A | type | Will always be set to "LEAD_ACTION" |
N/A | leadType | Type of the lead, will be set to "SPONSORED" for sponsored leads |
N/A | leadAction | Action performed on the lead, will be either "CREATED" or "DELETED" |
For example, consider the following JSON in the old payload format:
{
"createdTime": 1489428028784,
"accountUrn": "urn:li:sponsoredAccount:67890",
"campaignUrn": "urn:li:sponsoredCampaign:789012",
"creativeUrn": "urn:li:sponsoredCreative:11223344",
"formUrn": "urn:li:adForm:123456",
"adFormResponseUrn": "urn:li:adFormResponse:abc123-abab-abab-abab-abc123abc123"
}
The equivalent JSON in the new payload format would look like:
{
type: “LEAD_ACTION”,
leadGenFormResponse: "urn:li:leadGenFormResponse:abc123-abab-abab-abab-abc123abc123",
leadGenForm: "urn:li:versionedLeadGenForm:(urn:li:leadGenForm:123456, 1)",
owner: {"sponsoredAccount": "urn:li:sponsoredAccount:67890"},
associatedEntity: {"sponsoredCreative": "urn:li:sponsoredCreative:11223344"},
leadType: "SPONSORED"
leadAction: "CREATED"
occurredAt: 1489428028784
}
Duplicate Webhooks
Please note that legacy webhooks (LeadNotificationUrls) will continue to receive notifications in the old payload format until the legacy APIs reach end of life. This means that you will receive duplicate notifications if you have also subscribed using the same parameters via the new LeadNotifications API. Proper deduplication logic should be put in place.
Setting up a webhook via LeadNotificationUrls API vs LeadNotifications API
- LeadNotificationUrls API create request for a sponsored account webhook
curl -X POST 'https://api.linkedin.com/rest/leadNotificationUrls' \
--H 'X-Restli-Protocol-Version: 2.0.0' \
--H 'Authorization: Bearer {INSERT_TOKEN}' \
--H 'LinkedIn-Version: {version number in the format YYYYMM}' \
--H 'Content-Type: application/json' \
--data '{
"key": {
"developerApplication": "urn:li:developerApplication:12345",
"sponsoredEntity": "urn:li:sponsoredAccount:67890"
},
"status": "ACTIVE",
"url": "[https://www.example.com](https://eogi5ry4pv6izmq.m.pipedream.net)"
}'
- LeadNotifications API create request for a sponsored account webhook
curl -X POST 'https://api.linkedin.com/rest/leadNotifications”' \
--H 'X-Restli-Protocol-Version: 2.0.0' \
--H 'Authorization: Bearer {INSERT_TOKEN}' \
--H 'LinkedIn-Version: {version number in the format YYYYMM}' \
--H 'Content-Type: application/json' \
--data '{
"webhook": "https://eogi5ry4pv6izmq.m.pipedream.net",
"owner": {
"sponsoredAccount": "urn:li:sponsoredAccount:67890"
},
"leadType": "SPONSORED"
}'
FAQ
Q: If my developer app already had access to the legacy APIs and permissions, do I need to request access to the new Lead Sync API?
No. Your existing developer app should already have access to the new endpoints and permissions.
Q: What if I’ve migrated to the new lead sync APIs but I have not migrated any existing webhook subscriptions yet? This means I’m still receiving the legacy webhook notification payload but using the new Lead Sync APIs to get back the form response data. Can I use the new GET /leadFormResponses/{id}
endpoint using the id in the legacy webhook notification payload? E.g. urn:li:adFormResponse:abc123-abab-abab-abab-abc123abc123
Yes, however please note that the new /leadFormResponses/{id}
endpoint takes in an ID, abc123-abab-abab-abab-abc123abc123, and not an URN.
Response Decoration Migration
Below are examples of the top API response decoration usage and how to achieve the same via the new Lead Sync APIs. Some fields are not returned by default in the API request response therefore, to get all fields you may require, you will need to use field projection. Field projection allows you to choose exactly what fields you'd like returned in the request response.
If the additional metadata you seek is not returned, making a separate API request may be required. For example, to get the campaign group, make an API request to the GET campaign by ID endpoint and view the campaignGroupInfo
field.
Response decoration migration for adFormResponses API
adFormResponses | leadFormResponses | field projection example |
---|---|---|
form~(form(hiddenFields, landingPage, name, id)) | form.hiddenFields --> form.hiddenFields[] form.landingPage -->form.content.postSubmissionInfo.callToAction.callToActionTarget.landingPageUrl form.name --> form.name form.id --> form.id |
fields=form:(hiddenFields,name,id,content:(postSubmissionInfo:(callToAction:(callToActionTarget:(landingPageUrl))))) |
question~(name, question, typeSpecificQuestionDetails, predefinedField, questionId) | question.name --> form.content.questions[].name question.question --> form.content.questions[].question question.typeSpecificQuestionDetails --> form.content.questions[].questionDetails question.predefinedfield --> form.content.questions[].predefinedField question.questionId --> form.content.questions[].questionId |
fields=form:(content:(questions)) |
consent~(content, consentId, consentRequired) | consent.content --> form.content.legalInfo.consents[].consent consent.consentId --> form.content.legalInfo.consents[].Id consent.consentRequired --> form.content.legalInfo.consents[].checkRequired |
fields=form:(content:(legalInfo:(consents))) |
account~(id, name) | account.id --> owner.sponsoredAccount (parse out just the numbers)account.name --> ownerInfo.sponsoredAccountInfo.name |
fields=owner,ownerInfo:(sponsoredAccountInfo) |
campaign~(id, name, type) | campaign.id --> leadMetadataInfo.sponsoredLeadMetadataInfo.campaign.id (parse out just the numbers)campaign.name --> leadMetadataInfo.sponsoredLeadMetadataInfo.campaign.name campaign.type --> leadMetadataInfo.sponsoredLeadMetadataInfo.campaign.type |
fields=leadMetadataInfo:(sponsoredLeadMetadataInfo:(campaign:(id,name,type))) |
creative~(id, intendedStatus, content) | creative.id --> associatedEntityInfo.associatedCreative.id (parse out just the numbers)creative.intendedStatus --> associatedEntityInfo.associatedCreative.intendedStatus creative.content --> associatedEntityInfo.associatedCreative.content.reference (make additional API call to the /posts endpoint to get additional metadata if required) |
fields=associatedEntityInfo:(associatedCreative:(id,intendedStatus,content:(reference))) |
Response decoration migration for eventFormResponses API
eventFormResponses | leadFormResponses | field projection example |
---|---|---|
form~(hiddenFields, landingPage, name, id) | form~.hiddenFields --> form.hiddenFields[] form~.landingPage --> form.content.postSubmissionInfo.callToAction.landingPageUrl form~.name --> form.name form~.id --> form.id |
fields=form:(hiddenFields,name,id,content:(postSubmissionInfo:(callToAction:(callToActionTarget:(landingPageUrl))))) |
event~(name,id) | event~.name --> make an additional api call to the /events endpoint using the idevent~.id --> id |
/leadFormResponses?fields=id |
question~(name) | question~.name --> form.content.questions[].name |
fields=form:(content:(questions)) |
The examples in the table above are very specific to show how granular you can get with field projection however, for the data we want, getting that granular isn't necessary. The following example simplifies the request as either way, the json request response will need to be parsed. fields=ownerInfo,associatedEntityInfo,leadMetadataInfo,owner,leadType,versionedLeadGenFormUrn,id,submittedAt,testLead,formResponse,form:(hiddenFields,creationLocale,name,id,content)
.