LinkedIn Conversions
This guide covers how to submit conversions to LinkedIn Ads through the TNT Growth API.
Authentication
All requests require an x-api-key header with your client-specific API key provided by TNT Growth.
-H "x-api-key: YOUR_API_KEY"
Submitting a Conversion
Endpoint
POST /api/linkedin/events/add
Request Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
accountId | string | Yes | Your LinkedIn Ad Account ID (provided during onboarding) |
conversionId | string | Yes | LinkedIn Conversion ID from the conversion rule |
conversionTime | number | Yes | Unix timestamp (seconds) of when the conversion occurred |
userData | object | No | Customer identifiers for matching (see below) |
userInfo | object | No | Additional user info — { firstName, lastName } |
metadata | object | No | Conversion goal metadata (see below) |
ingestionSource | string | No | Origin of the event (e.g. pixel, conversion-api) |
tntId | string | No | The attribution handle from the pixel. A resolved session fills userData.liFatId and any missing UTM fields |
utmSource | string | No | UTM source parameter (e.g. linkedin) — stored for attribution reporting, and also decides delivery when it names a different paid platform. See When we withhold a conversion |
utmMedium | string | No | UTM medium parameter (e.g. cpc) — stored for attribution reporting |
utmCampaign | string | No | UTM campaign name — stored for attribution reporting |
utmTerm | string | No | UTM search keyword — stored for attribution reporting |
utmContent | string | No | UTM content parameter (ad/creative variant) — stored for attribution reporting |
User Data Fields (userData)
Include as many identifiers as possible to improve match quality.
| Field | Type | Description |
|---|---|---|
email | string | Customer email address |
phone | string | Customer phone number |
liFatId | string | LinkedIn First-Party Ad Tracking ID captured from the ad click |
companyName | string | Customer company name |
Metadata Fields (metadata)
| Field | Type | Description |
|---|---|---|
customConversionGoalId | string | LinkedIn custom conversion goal ID |
customConversionGoalName | string | LinkedIn custom conversion goal name |
Example Request
curl -X POST https://flex.tntgrowth.io/api/linkedin/events/add \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"accountId": "510876543",
"conversionId": "12345678",
"conversionTime": 1778043599,
"userData": {
"email": "customer@example.com",
"phone": "+15551234567",
"liFatId": "AbCdEfGhIjKlMnOp"
},
"userInfo": {
"firstName": "Jane",
"lastName": "Doe"
},
"tntId": "9f2b7c1e-4d3a-4f8b-9c2e-1a5d7e0b4c63",
"utmSource": "linkedin",
"utmMedium": "paid-social",
"utmCampaign": "demand-gen-q2",
"utmTerm": "treatment programs",
"utmContent": "sponsored-content-variant-b"
}'
Example Response
{
"message": "Conversion successfully queued for LinkedIn! 510876543",
"data": {
"submissionId": "8f3c2d10-7e6b-4a51-9d8c-3b2e1f0a9b8c",
"duplicate": false
}
}
When a conversion is dedup-skipped (the same event was submitted within the last 24 hours), submissionId is null and duplicate is true.
Checking Submission Status
After submitting a conversion, use the submission ID to check processing status.
Endpoint
GET /api/conversions/submissions/:submissionId
Example Request
curl https://flex.tntgrowth.io/api/conversions/submissions/8f3c2d10-7e6b-4a51-9d8c-3b2e1f0a9b8c \
-H "x-api-key: YOUR_API_KEY"
Example Response
{
"id": "8f3c2d10-7e6b-4a51-9d8c-3b2e1f0a9b8c",
"source": "LINKEDIN",
"externalAccountId": "510876543",
"expectedCount": 1,
"createdAt": "2026-05-11T12:00:00.000Z",
"scheduledFor": null,
"counts": {
"success": 1,
"failed": 0
},
"status": "complete"
}
Status Values
| Status | Description |
|---|---|
pending | Conversion is queued and waiting to be processed |
pending_delivery | Submission is scheduled for future delivery. Currently only emitted by Google Ads ingest paths; LinkedIn submissions never enter this state. |
complete | Every conversion reached a final outcome and none was rejected. Usually means all were delivered — but a withheld conversion also finishes here, so compare counts.success with expectedCount if you need delivery confirmed |
complete_with_errors | Processing finished but some conversions failed — use GET /api/conversions/linkedin/failed?submissionId=<id> to fetch the actual error messages |
stalled | Processing has not progressed — contact support |
Withheld Conversions
A conversion is withheld when its attribution says another paid channel served the click — a utmSource naming
a different platform, with no LinkedIn click ID beside it. Uploading it would train LinkedIn's bidding on traffic
LinkedIn never sent, so we hold it back instead. The full rules, and the utm_source values that count as a claim,
are in When we withhold a conversion from a platform.
This is not an error. A withheld pair appears in counts.withheld, is excluded from counts.success, and
never counts as failed — failed means the platform rejected it. A submission whose only non-delivered pairs
were withheld finishes as complete, so compare counts.success with expectedCount if you need delivery
confirmed.
{
"expectedCount": 2,
"counts": {
"success": 1,
"failed": 0,
"withheld": 1
},
"withheld": [
{
"reason": "cross_channel",
"claimedPlatform": "google",
"count": 1
}
],
"status": "complete"
}
Both counts.withheld and the withheld array are present only when something was withheld, so a submission
with nothing withheld returns exactly the shape it always has. Each entry carries reason (always
cross_channel today), claimedPlatform (google, meta, bing, tiktok, linkedin, reddit or openai)
and count; the entries sum to counts.withheld. Nothing is discarded — the payload is kept, so a withheld
conversion can still be delivered later if your tagging or the rules change. Re-sending the same payload will
be withheld again; send the LinkedIn click ID alongside utmSource and the click ID wins.
The scheduledFor field is always null for LinkedIn submissions and is included only for schema parity with Google Ads, which uses it for deferred delivery.
Inspecting Failed Conversions In Your Batch
When a submission ends with complete_with_errors, the submission status endpoint surfaces only the count of failed rows. Fetch the actual error messages with:
GET /api/conversions/linkedin/failed?submissionId=<submissionId>
The response payload mirrors the Google Ads failed-conversion shape — see Google Ads → Inspecting Failed Conversions In Your Batch.
Best Practices
- Include
liFatIdwhen available — LinkedIn's first-party tracking ID provides the strongest attribution signal - Send
userDatawith email and/or phone to improve match rates for offline events - Use accurate timestamps —
conversionTimeshould reflect when the conversion actually occurred - Match
conversionIdto the correct conversion rule in your LinkedIn Campaign Manager - Poll with a delay — wait 30-60 seconds after submission before checking status
- Send
tntIdwhen your backend has it — the attribution handle recovers the click ID and UTMs captured on the visitor's original ad click, which is often the only thing that attributes an offline conversion at all