Context
Currently InSender always posts as a personal profile, building the author URN as urn:li:person:{IN_OWNER}. LinkedIn also supports posting to organization (company) pages via the same UGC Posts API, using urn:li:organization:{org_id} as the author.
This issue tracks the full implementation of the company-page sender path.
LinkedIn Platform Prerequisites
Before writing any code, the following must be configured in the LinkedIn Developer Portal:
1. LinkedIn Developer App setup
- Go to https://www.linkedin.com/developers/apps and create or select your app.
- In the Products tab, request access to:
- Share on LinkedIn (required for UGC post creation)
- Marketing Developer Platform (required to post as an organization)
- Under Auth → OAuth 2.0 scopes, ensure these scopes are authorised:
w_member_social – post as a person
w_organization_social – required to post as an organization
r_organization_social – read org posts (useful for debugging)
- Verify that your LinkedIn account has the Admin role on the target organization page. Without this, the API will return a
403 Forbidden.
2. Obtain a token with the correct scopes
Follow the LinkedIn OAuth 2.0 Authorization Code flow and include w_organization_social in the scope parameter:
https://www.linkedin.com/oauth/v2/authorization
?response_type=code
&client_id={YOUR_CLIENT_ID}
&redirect_uri={YOUR_REDIRECT_URI}
&scope=w_member_social%20w_organization_social%20r_organization_social
Exchange the authorization code for an access token via:
POST https://www.linkedin.com/oauth/v2/accessToken
Store the resulting token in IN_ACCESS_TOKEN.
3. Find your organization ID
Call:
GET https://api.linkedin.com/v2/organizationalEntityAcls?q=roleAssignee&role=ADMINISTRATOR&projection=(elements*(organizationalTarget~(id,localizedName)))
Authorization: Bearer {IN_ACCESS_TOKEN}
X-Restli-Protocol-Version: 2.0.0
The id field of the organization element is the numeric ID to use in the URN (e.g. 98765432).
Alternatively, find it in the LinkedIn company page URL:
https://www.linkedin.com/company/YOUR_COMPANY/admin/ → the URL will contain the numeric ID.
Environment Variables Required
Add one new variable to src/local.settings.json.example and docs/configuration.md:
| Variable |
Type |
Required |
Description |
IN_ORG_ID |
string |
Optional |
Numeric LinkedIn organization ID. When set, posts are authored as urn:li:organization:{IN_ORG_ID}. When absent, posts fall back to the personal profile via urn:li:person:{IN_OWNER}. |
Implementation Plan
src/SenderPlugins/InSender.cs
Replace the hardcoded urn:li:person:{inOwner} with a helper that resolves the correct URN:
private static string ResolveAuthorUrn()
{
var orgId = Environment.GetEnvironmentVariable("IN_ORG_ID");
if (!string.IsNullOrWhiteSpace(orgId))
return $"urn:li:organization:{orgId}";
var personId = Environment.GetEnvironmentVariable("IN_OWNER")
?? throw new InvalidOperationException("Either IN_OWNER or IN_ORG_ID must be set.");
return $"urn:li:person:{personId}";
}
Replace the line:
var inOwner = Environment.GetEnvironmentVariable("IN_OWNER")
?? throw new InvalidOperationException("IN_OWNER environment variable is not set.");
with:
var author = ResolveAuthorUrn();
Update the payload builder to use author instead of $"urn:li:person:{inOwner}":
return new
{
author = author, // ← was: $"urn:li:person:{owner}"
lifecycleState = "PUBLISHED",
specificContent,
visibility
};
generatePayLoad signature update
Change:
private dynamic generatePayLoad(string? asset, string owner, string summary)
to:
private dynamic generatePayLoad(string? asset, string authorUrn, string summary)
and update all call sites accordingly.
Image upload for organization posts
Note: the current registerUpload recipe (urn:li:digitalmediaRecipe:feedshare-image) and owner URN in the init payload must also reflect the organization URN when posting as an organization:
owner = authorUrn, // urn:li:organization:... or urn:li:person:...
Tests to Add
In tests/SenderPlugins/InSenderTests.cs:
ResolveAuthorUrn_WhenOrgIdIsSet_ReturnsOrganizationUrn
ResolveAuthorUrn_WhenOrgIdIsAbsent_ReturnsPersonUrn
ResolveAuthorUrn_WhenBothAreAbsent_ThrowsInvalidOperationException
SendAsync_WhenPostingAsOrganization_PayloadContainsOrgUrn
Documentation to Update
Acceptance Criteria
Context
Currently
InSenderalways posts as a personal profile, building the author URN asurn:li:person:{IN_OWNER}. LinkedIn also supports posting to organization (company) pages via the same UGC Posts API, usingurn:li:organization:{org_id}as the author.This issue tracks the full implementation of the company-page sender path.
LinkedIn Platform Prerequisites
Before writing any code, the following must be configured in the LinkedIn Developer Portal:
1. LinkedIn Developer App setup
w_member_social– post as a personw_organization_social– required to post as an organizationr_organization_social– read org posts (useful for debugging)403 Forbidden.2. Obtain a token with the correct scopes
Follow the LinkedIn OAuth 2.0 Authorization Code flow and include
w_organization_socialin thescopeparameter:Exchange the authorization code for an access token via:
Store the resulting token in
IN_ACCESS_TOKEN.3. Find your organization ID
Call:
The
idfield of the organization element is the numeric ID to use in the URN (e.g.98765432).Alternatively, find it in the LinkedIn company page URL:
https://www.linkedin.com/company/YOUR_COMPANY/admin/→ the URL will contain the numeric ID.Environment Variables Required
Add one new variable to
src/local.settings.json.exampleanddocs/configuration.md:IN_ORG_IDurn:li:organization:{IN_ORG_ID}. When absent, posts fall back to the personal profile viaurn:li:person:{IN_OWNER}.Implementation Plan
src/SenderPlugins/InSender.csReplace the hardcoded
urn:li:person:{inOwner}with a helper that resolves the correct URN:Replace the line:
with:
Update the payload builder to use
authorinstead of$"urn:li:person:{inOwner}":generatePayLoadsignature updateChange:
to:
and update all call sites accordingly.
Image upload for organization posts
Note: the current
registerUploadrecipe (urn:li:digitalmediaRecipe:feedshare-image) and owner URN in the init payload must also reflect the organization URN when posting as an organization:Tests to Add
In
tests/SenderPlugins/InSenderTests.cs:ResolveAuthorUrn_WhenOrgIdIsSet_ReturnsOrganizationUrnResolveAuthorUrn_WhenOrgIdIsAbsent_ReturnsPersonUrnResolveAuthorUrn_WhenBothAreAbsent_ThrowsInvalidOperationExceptionSendAsync_WhenPostingAsOrganization_PayloadContainsOrgUrnDocumentation to Update
docs/configuration.md– addIN_ORG_IDrowsrc/local.settings.json.example– add"IN_ORG_ID": ""with commentREADME.md– update LinkedIn description to mention org page supportdocs/getting-started.md– add a note explaining theIN_OWNERvsIN_ORG_IDselectionAcceptance Criteria
IN_ORG_IDis set andIN_OWNERis also set, posts are authored asurn:li:organization:{IN_ORG_ID}IN_OWNERis set, behaviour is unchanged (personal profile)InSenderthrowsInvalidOperationExceptionat runtime (fail-fast)