Odoo Community Association

Social Media Linkedin

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

This module publishes on the LinkedIn company pages an Odoo user administrates, and keeps the daily figures of those pages.

Everything it asks LinkedIn for costs a fixed number of calls per account or per publication, so what it costs does not change whether a page published once or ten thousand times. Reading back what the page already published — importing the publications, their comments and their reactions — grows with that history and lives in Social Media LinkedIn Sync, which installs on its own as soon as Social Media Sync is present.

Main features:

Statistics account

  1. The eye icon: Total number of views, which may include multiple views by the same user.

  2. The hand icon: the interactions (clicks, likes, comments and shares) the page accumulated over the days of the series.

  3. The star icon: the engagement of the account, its interactions over its impressions, shown as a percentage. The engagement LinkedIn reports by day is kept on each row of the time series and read in the graph and pivot views; it is never averaged into the card.

    STATISTICS_ACCOUNT

Table of contents

To configure this module, you need to:

Please note that you must have a developer account. The steps required for using it are defined below:

Before creating this developer account, you must have a partner company on your LinkedIn account and be an administrator.

Only company pages (organizations) can be linked, never personal profiles: a post is always published on behalf of the organization. Odoo only reads the organizations where the member who authorizes is an ADMINISTRATOR whose invitation has already been accepted (APPROVED state). If your role is still pending, LinkedIn does not return the organization and the wizard ends without creating any account.

The connector needs no additional Python library: it uses requests, which is already shipped with Odoo, so there is nothing to install on the server besides the module itself.

  • Go to https://developer.linkedin.com/

  • Create a new Developer App with the Create app button.

    BUTTON_CREATE_APP

  • Fill in the requested fields. In the Privacy Policy URL field, copy your company’s URL.

    FORM_CREATE_APP

  • Once the app is created, go to the My Apps menu and you will see the newly created app; select it.

  • Within the app, in the Settings tab, verify your company. You will see a button that says Verify. Click it, and in the window that appears in the lower left corner, click the Generate URL button. Copy the generated URL into your browser and accept.

  • Then go to the Products tab and request access to the products granting the scopes the installed modules ask for:

    • Community Management API, which grants the three scopes this connector needs: rw_organization_admin, w_organization_social and r_organization_social. It is not self-serve: LinkedIn reviews the request, and until it is approved no account can be associated at all — the consent screen is all or nothing, so a scope the App was not granted fails the whole authorization, not only the calls that need it.
    • Advertising API, only if Social Media Advertising LinkedIn is installed, for r_ads, rw_ads and r_ads_reporting. It is not self-serve either.

    PRODUCTS

    Note that some products require you to fill out a form; you must do so, otherwise, the necessary scopes for basic use of your account will not be enabled. The products, the scopes they grant and the versions of the API are described in the LinkedIn Marketing API documentation and in its versioning guide.

  • After requesting the aforementioned Products, go to the Auth tab and you will see all the enabled scopes at the bottom.

    Each module asks for the permissions its own calls consume, so what is requested depends on what is installed:

    Module Scopes What needs them
    Social Media LinkedIn rw_organization_admin Listing the organizations the member administers, and the figures of the page
      w_organization_social Publishing and deleting a post, and uploading its images and videos
      r_organization_social Reading a publication back, which is what tells one deleted on LinkedIn from one still online
    Social Media LinkedIn Sync r_organization_social Reading the feed of the page, the comments and the reactions
    Social Media Advertising LinkedIn r_ads, rw_ads, r_ads_reporting The advertising accounts, their campaigns and their figures

    All of them have to appear enabled in the Auth tab of your App; if one is missing, LinkedIn refuses the whole authorization and the Product that grants it has to be requested. LinkedIn does not let the member pick part of them either: the consent screen is all or nothing, which is why nothing is requested “just in case”.

    What LinkedIn granted is stored on the account, in the Granted Scopes field of its Configuration tab, so a permission error can be read there instead of in the logs. That same field is what the next authorization requests, and it can be edited:

    • Enable the new Product on your LinkedIn App and wait for it to be approved.
    • Add the scopes it grants to the Granted Scopes field of the account.
    • Open Update account, tick Update keys and authorize again on LinkedIn.

    Editing the field on its own changes nothing, and ticking only Update token is not enough either: refreshing a token keeps the scopes it was granted, only a new authorization can ask for more. Go straight to Update account after editing the field, because Validate token and any token refresh rewrite it with the scopes LinkedIn reports, and the additions typed by hand are lost. Beware that a scope the Products of your App do not grant makes LinkedIn refuse the whole authorization, and the account cannot be authorized again until it is removed from the field.

    Every call to the versioned REST API carries the LinkedIn-Version: 202607 and X-Restli-Protocol-Version: 2.0.0 headers. The OAuth calls of the association — the code-for-token exchange and the token introspection — and the download of the organization logo do not. LinkedIn retires each version of the API about a year after publishing it, so the module has to be updated periodically: once the version is no longer supported, LinkedIn answers every request with a version error.

  • At the top of the aforementioned tab, you will see the Client ID and Primary Client Secret information.

  • Configure the access points for which you want to use the account. Follow these steps:

    • Go to Settings > Technical > Parameters > System Parameters.
    • Search for web.base.url
    • Copy the base URL and concatenate it with the endpoint. Then, in your LinkedIn Developer Account, on the Authentication tab, in the Authorized Redirect URLs for Your App section, add a new item. * Example: web.base.url: http://192.168.1.7:8017 endpoint: /linkedin/callback linkedin_url: http://192.168.1.7:8017/linkedin/callback

    CONFIGURE_URL_CALLBACK

Registering the Client ID and Client Secret. Integration of a user account.

  • Go to Social Media > Configuration > Social Media

  • Click on the Associate Account button for the desired social media.

    ASSOCIATE_ACCOUNT

  • A wizard will open for you to add the Client ID and Client Secret obtained from your developer account.

    WIZARD_ASSOCIATE_ACCOUNT

  • By clicking the Associate button here, you’ll be taken to a LinkedIn authentication page. Once you validate your information, LinkedIn brings you back to Odoo: if the association succeeded you land directly on the Social Media Dashboard with a success notification, and if it failed you come back to the start screen with the notification of the error.

    AUTHORIZE_ACCOUNT

  • Once you have completed these steps and everything is working correctly, you can see your account in Social Media > Configuration > Accounts

  • After the account is associated, the daily figures of the page are read and the card of the dashboard is filled. Importing what the page already published needs Social Media LinkedIn Sync.

  • Note that the account credentials (Client ID, Client Secret and tokens) are masked and only visible to administrator users.

  • The authorization flow is bound to the session that started it: the state token sent to LinkedIn only works for the user who opened the wizard, and it is discarded once the flow ends, so the same authorization cannot be replayed. If the state does not match, the association is refused with Invalid OAuth state token. Please restart the account association process. The flow itself is the one described in the authorization code flow of LinkedIn.

  • If the LinkedIn organization is already linked to an account of another Odoo user, the association is refused and nothing is overwritten: only the responsible user of the account and the Social Media / Administrator group can relink it.

  • Every account has to use its own credentials: if the Client ID and the Client Secret are already registered on another account, the association is refused with An account with this information already exists; please also check archived accounts. The check includes the archived accounts, which keep their credentials, so check the Archived filter as well before trying again. To reauthorize an existing account, use its Update account button instead of the wizard.

System parameters

Nothing here has to be set for the connector to work: each key defaults to the value the code carries, and it only exists once it is written by hand in Settings > Technical > Parameters > System Parameters. They are bounded when they are read, so a value outside its range is brought back into it instead of being obeyed.

Parameter Default Unit Bounds
social_media_linkedin.video_poll_attempts 30 polls at least 1, and at most what the 600 second ceiling allows
social_media_linkedin.video_poll_delay 2 seconds between polls at least 1

The two are the wait LinkedIn needs to finish processing an uploaded video before the post can be published, and what matters is their product: it is time the publication spends inside its own transaction. Past the limit_time_real of the deployment — 120 seconds by default, and the scheduled actions inherit it — the worker is killed with the video already uploaded on LinkedIn and nothing published in Odoo. That is why the number of polls is cut down to what 600 seconds allow at the delay in force, and why raising the wait for a long video means raising limit_time_real as well. A delay longer than the ceiling itself is not cut: it becomes one single wait, and setting one is asking for the worker to be killed.

Below their bounds the numbers stop making sense rather than merely being small: zero polls publishes nothing without ever asking LinkedIn, and a delay under a second turns the wait into a burst against an API whose rate limit the module does not manage.

Usage

List of posts generated from Odoo.

Only posts generated using Odoo are displayed.

  • Go to Social Media > Posts
  • Opening a publication, from its form or from its card on the dashboard, reads it on LinkedIn first. One that was deleted there is reported as The post does not exist or has been deleted. and marked as Deleted on the spot, keeping its reference. Only a 404 counts as a deletion: a lost page role or a throttled application leaves the publication alone, because a publication is not gone just because Odoo could not read it.

Generate a post.

This feature acts as a template for generating multiple posts from a single view, depending on the selected accounts.

  • Go to Social Media > Posts > New or Go to Social Media > Dashboard > Add Post
  • Fill in the required fields
  • When the post is created, every LinkedIn account of the active company is selected by default in Accounts; remove the ones you do not want to use before publishing. CREATE_POST
  • Save
  • Click on the Post button
  • LinkedIn publishes either images or a video, never both: when the post carries a video, its images are left out. The preview of the post says so by showing the video alone, so what is previewed is what LinkedIn receives, and a banner on the form explains it as well.
  • A publication shares the images and the video of its post, so the dashboard card shows them as soon as LinkedIn accepts the post and nothing else has to run. What LinkedIn made of each media is recorded on the publication, which is what lets an image deleted on LinkedIn leave the card while the one in the post stays; noticing that deletion needs Social Media LinkedIn Sync.

Update token, client ID, client Secret and organization data

  • Go to Social Media > Configuration > Accounts

  • Select the account

  • Click on the Update account button

    BUTTON_UPDATE_ACCOUNT

  • In the wizard that appears, if none of the checkboxes are selected and the Update button is pressed, the system will update only the organization’s data.

  • If the Update keys checkbox is selected, the current Client ID is proposed to the administrator users, the only ones allowed to read it, and the Client Secret has to be typed again: the stored secret is never sent to the browser. Authentication is then performed again through LinkedIn, and the keys are only written on the account once LinkedIn has accepted them, so an authorization left halfway keeps the credentials that still work.

    UPDATE_KEYS

  • Selecting the Update token checkbox will update the current token.

    UPDATE_TOKEN

Validate the token

  • Go to Social Media > Configuration > Accounts

  • Select the account and open the Configuration tab

  • Click on the Validate token button. It always asks LinkedIn whether the token is still active, because a token can be revoked there long before the stored expiry dates: a notification confirms that it is valid, and if it is not, the token is renewed. Outside the renewal window, the check made before every call to LinkedIn uses the stored dates, so it costs no extra request.

    VALIDATE_TOKEN

Archive Account Linkedin

  • Go to Social Media > Configuration > Accounts

  • Select the account and pick Archive in the Actions menu of the form.

    ARCHIVE_ACCOUNT

  • Please note that all data associated with this account will be archived.

  • If you associate the same LinkedIn account again later, the archived account and its related data will be reactivated instead of creating a duplicate.

  • An archived account can be deleted permanently with the Delete permanently button, only available to a social media administrator. The LinkedIn publications stay online, only the Odoo history is removed.

Uninstalling the module

Uninstalling Social Media Linkedin does not delete the accounts nor their publication history:

  • The access token and the refresh token are cleared, so no credential outlives the module.
  • The LinkedIn accounts are archived, together with their posts.
  • The LinkedIn specific data is lost, because Odoo drops the columns of an uninstalled module: the application Client ID and Client Secret.
  • The identifier of each account and publication on LinkedIn is kept, so installing the module back and associating the account again reactivates the archived history and updates it, instead of importing everything as duplicated records.

Time series of the account

  • LinkedIn is asked for its figures by day, with the `organizationalEntityShareStatistics <https://learn.microsoft.com/en-us/linkedin/marketing/community-management/organizations/share-statistics>`__ finder and timeGranularityType=DAY, and every bucket it answers becomes one row of Social Media > Statistics. Nothing is invented for the days it reports nothing for.
  • Right after the account is linked, the rewrite window is read and the whole period LinkedIn reports is asked for straight after, so the card of the dashboard is filled with the year and not only with the last days. No synchronization module is needed for that.
  • The finder answers at most 100 daily buckets per call and does not paginate, so a period wider than that is asked for in several calls, one after another, instead of coming back cut at the hundredth day.
  • The whole period is read only once. It is then skipped, because the rewrite window is the only thing written on every pass and a row older than it can only come from that first reading. If it failed –LinkedIn refused the call, the token had just been issued– the Rebuild statistics history button of the account form asks for it again.
  • The last week is asked for again on every pass of the two-hourly check, and by the Update statistics button of the account form. LinkedIn revises figures of days already past, so the recent ones are rewritten instead of being trusted as final; the days before that window stay as they were left.
  • The Rebuild statistics history button asks LinkedIn for the daily figures of the page again, as far back as it reports them, and rewrites the time series with them. It does not import publications: what it rebuilds is the graph of the account. Bringing in the publications themselves is what Social Media LinkedIn Sync does.
  • An account whose statistics LinkedIn refuses is reported and skipped, and the rest of the accounts keep the rows already written for them.
  • The account is asked for its organization page as a whole, which includes what was published before Odoo managed the account or outside of it. The figures are therefore not the sum of what Odoo imported and are not meant to be compared against it.

Figures of a publication

  • The figures of the publications of the last 30 days are read back from LinkedIn once a day, by the Social: Refresh the statistics of the recent publications scheduled action, and on the spot by the Update button of the dashboard and Update statistics of the account form.
  • Nothing walks the feed to do it: Odoo already knows the URN of every publication it is asking about, so a whole page of them is answered by at most three callsorganizationalEntityShareStatistics once for the shares and once for the UGC posts, and socialActions for the likes and the comments of the UGC posts, which LinkedIn documents as the up-to-date ones.
  • Only the kinds the page really carries are asked for. The publications Odoo publishes are shares, so a page of them costs one call: the finder answers their likes and comments inside the same block, and socialActions is only asked about UGC posts, which are the publications made outside Odoo.
  • Those URNs travel in the query string of a finder LinkedIn documents as not paginated, so a page whose URNs do not fit in 4 KB is asked for in as many calls as it takes, around 95 publications each. That is the only thing that adds a call to the three.
  • A publication missing from the answer is one nobody interacted with: the finder leaves out the entities with no activity at all, so its figures are written as zeros and its date as read all the same.
  • No new permission is needed: the figures of a publication come from organizationalEntityShareStatistics and socialActions, which the scopes already requested when the account was associated cover, so an account already associated is not asked to authorize anything again.
  • Reading the publications LinkedIn has and Odoo does not is another matter, and it stays in Social Media LinkedIn Sync: that one costs one call per page of the feed.

LinkedIn tokens

  • The access token of LinkedIn lasts two months and its refresh token a year, as stated in the refresh tokens documentation. Within the week before the expiry date, and on every run of the scheduled action Social: Checking social media updates and before publishing, Odoo asks LinkedIn whether the token is still active (introspectToken endpoint) and only renews it when LinkedIn answers that it is not. Outside of that window the check is answered with the stored dates and costs no request. Nothing has to be done for a post planned weeks ahead.
  • If LinkedIn refuses the token anyway, it is renewed and the publication is sent again straight away.
  • Once the refresh token expires, or the authorization is revoked from LinkedIn, no renewal is possible: the account shows the update warning and has to be authorized again with Update account, which is the only step that needs the browser.

LinkedIn limits and validations

What LinkedIn refuses is checked in Odoo before the publication is sent. The post shows the reason while it is being written, saving is never blocked, and the publication of the account that raises the objection is refused instead of being sent and failing on LinkedIn. The same checks are applied when the post reaches the publication through an import or an RPC call, so nothing gets past them.

  • The message is checked against 3000 characters, the length the commentary field of the Posts API takes. A longer message is reported on the post and the publication is not sent.
  • The images are checked against 20 per post, against the formats LinkedIn publishes, JPG, PNG and GIF, and against 10 MB each. One image is published as a single image and two or more as a multi-image post. The message names the files that cannot be published.
  • The video is checked against one per post, against MP4 and against 500 MB.
  • A post carrying images and a video is not refused: LinkedIn publishes the video and leaves the images out, so the post is told what goes out instead of being stopped. That is also why the image rules are not applied at all to a post carrying a video — its images decide nothing.
  • The file picker only filters what the browser proposes, so a file added by drag and drop reaches the checks all the same, and they are what refuses the ones LinkedIn does not take.
  • What is left to LinkedIn is everything about the content itself: the dimensions and the aspect ratio of an image, the codecs and the duration of a video, the text it reads as spam. Nothing of that is checked here, and a publication LinkedIn refuses for one of those reasons is left as Failed with the validation error it answered.
  • Every call to LinkedIn has a timeout of 10 seconds, not configurable. If LinkedIn or the connection take longer, the operation fails with Error connecting to LinkedIn and has to be retried; a publication is left as Failed and can be sent again with the Post button.
  • An account without an access token does not publish: this is what happens after uninstalling and installing the module back, which clears the credentials. The line is left as Failed stating that the account has no access token, and the account shows the update warning. Authorize it again with Update account.
  • Recommending a publication or one of its comments, and reading those comments back, is served by Social Media LinkedIn Sync: this module draws none of those buttons and never calls the Reactions API.
  • Deleting a publication from the dashboard deletes it on LinkedIn first. If LinkedIn does not confirm the deletion, the operation is cancelled with Error deleting LinkedIn post and the record is kept in Odoo, so the two sides never get out of sync.

Video upload

  • LinkedIn decides how a video is split: initializeUpload answers one instruction per part, of 4 MiB each except the last one, and every part is uploaded with its own request. The identifiers LinkedIn returns for the parts are sent back to finalizeUpload in the same order, so the video is put together as it was cut. A 22 MB video takes 6 parts, and publishing then waits for LinkedIn to finish processing it, up to 30 polls two seconds apart.

    https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/videos-api

  • A post publishes a single video, so a post carrying several is refused before uploading anything: LinkedIn would only keep the first one and the others would be transferred and processed for nothing.

  • LinkedIn processes an uploaded video before it can be published, so publishing a post with a video waits until the video is available: 30 attempts every 2 seconds, which a long video may need more than. Both numbers are system parameters, described in CONFIGURE.

  • The publication shares the video of its post the same way it shares its images, and what LinkedIn made of it is recorded in its media references. The camera icon without a count is the fallback drawn for a publication that carries only the has video flag — one imported from LinkedIn, whose video was never downloaded and can only be watched there.

Publishing options

  • Every publication is created public, in the main feed, with no targeting by country, language or industry, and letting it be shared: visibility, feedDistribution, targetEntities and thirdPartyDistributionChannels are fixed in the code.

  • Scheduling is not delegated to LinkedIn either: the Odoo scheduled action is the one that publishes when the date arrives.

    https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api

Errors reported by LinkedIn

  • When LinkedIn rejects a request because of its content, its answer only says that the validation failed and lists the rejected fields apart, in the shape described in its error handling guide. Those explanations are the ones shown in Odoo, one per line, so the message names the field and the rule that was broken instead of the generic summary.

Known issues / Roadmap

Publishing options are not configurable

Duration, codecs, dimensions and aspect ratio of a video are not checked

  • Odoo does not read them before uploading: those limits are the ones of the Videos API and LinkedIn applies them while processing, so a video that breaks one of them is transferred whole and rejected afterwards, in the processing phase, with LinkedIn could not process the video. The size, 500 MB, and the MP4 format are checked in Odoo, on the post while it is written and again before the publication is sent.

Rate limits are not handled

  • The module does not handle the throttle limits of LinkedIn, applied per day and per application. When LinkedIn answers with a limit error, the operation is recorded as failed like any other error and has to be retried later by hand; only a credential rejection triggers an automatic retry, and only once.

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed feedback.

Do not contact contributors directly about support or help with technical issues.

Credits

Authors

  • Binhex

Contributors

Other credits

This work has been funded by AEOdoo (Asociación Española de Odoo - https://www.aeodoo.org)

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

Current maintainer:

edescalona

This module is part of the OCA/social project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.