Skip to main content
Odoo Community Association

Social Media LinkedIn Sync

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

This module brings back into Odoo what a LinkedIn page already published: the posts themselves, the figures each of them collected, and their comments and reactions.

It is the LinkedIn half of Social Media Sync, split from Social Media Linkedin along the same line: what a call costs. The connector asks LinkedIn for a fixed number of things per account — publish, delete, the daily figures of the whole page — and that number does not change whether the page published once or ten thousand times. Everything whose cost grows with the history of the page lives here: reading the feed one page at a time, asking LinkedIn about the publications missing from it a hundred URNs at a time, and one call per comment thread. The figures of the publications are read by Social Media Linkedin, in as many calls as the 4 KB limit of the query string needs, whoever hands it the identifiers.

An Odoo that only publishes on LinkedIn installs Social Media Linkedin alone and pays for none of it.

Main features:

  • Import of the publications of the page and of the statistics each of them collected, on demand and through the scheduled actions of Social Media Sync.

  • Full resynchronization of a page, the only pass that notices a publication deleted on LinkedIn. Missing from the feed never marks anything on its own: LinkedIn is asked about each suspect by its URN, one call per hundred of them, and only a 404 writes the deletion. The Posts API publishes asynchronously (PUBLISH_REQUESTED), answers the author finder in reader context, and states that a page returning fewer results than asked is not the end of the feed — so a publication that is alive can be absent from a listing read whole.

  • Publications deleted on LinkedIn recognised while the dashboard is being used: a reaction or a comment answered with a 404 makes Odoo ask LinkedIn about the publication itself, and only its confirmation marks the line as deleted.

  • Comment threads read from the dashboard, with their replies, and Recommend on a publication and on each of its comments, which also withdraws the reaction it made.

  • Detection of pages that moved since the last import, which puts a notice on the dashboard inviting the user to press Update. It costs at most two calls per account, and reuses the daily figures the connector already read on the same pass instead of asking LinkedIn for the same days twice.

Table of contents

Installation

This module is installed on its own as soon as Social Media Sync and Social Media Linkedin are both present, which is what a bridge module is for: the choice to synchronize was already made by whoever installed Social Media Sync, and this only keeps a LinkedIn page from being left without the half that belongs to it.

Installing Social Media Linkedin alone is a valid installation: the account is linked, publishes, deletes and shows the daily figures of its page.

The mark the update check compares against

social.account.linkedin_statistics_checkpoint is stored by this module and holds the daily figures LinkedIn reported for the whole page as of the last import. An account that has no mark yet is given one on the first pass of the bihourly check and announced as having nothing to import, so installing this module on a database that already publishes on LinkedIn does not put the notice up on every account at once.

Configuration

Nothing has to be configured for this module to work: it uses the LinkedIn accounts and credentials of Social Media Linkedin, and the scheduled actions of Social Media Sync.

It asks LinkedIn for nothing the connector does not already ask for. Reading the feed of a page, its comments and its reactions travels on r_organization_social, which Social Media Linkedin requests on its own, granted by the Community Management API product that connector already needs.

What does not follow on its own is the accounts already associated. An access token keeps the scopes it was issued with, so an account authorized before that permission was requested does not hold it and its history cannot be imported until it is authorized again. Refreshing the token is not enough: open the account, press Update account with Update keys ticked and authorize on LinkedIn again.

The accounts concerned say so themselves, so nothing has to be looked up: a warning is drawn on the account form for as long as the permission is missing, the responsible user is notified by the check that runs every two hours, and the import refuses with the name of the missing permission instead of the bare error LinkedIn answers.

System parameters

One key is worth knowing about, and it is only written by hand in Settings > Technical > Parameters > System Parameters: until then the module uses the default the code carries. It is bounded when it is read, so a value outside its range is brought back into it.

Parameter

Default

Unit

Bounds

social_media_linkedin_sync.posts_max_pages

50

pages of 100 publications

1 to 500

It is how far into the feed of a page one pass reads. The default covers five thousand publications, and it is not a limit of LinkedIn: it is what keeps a feed that never ends from looping forever.

A page with a longer history has to raise it. A feed read short comes back partial, and the weekly full resync — the only pass that notices a publication deleted on LinkedIn — does not look for deletions in a feed it could not read whole. Nothing breaks and nothing is said beyond a warning in the log naming the number of pages, so the symptom is publications that stay posted in Odoo after being deleted on LinkedIn.

Raising it costs calls: each page is one call against the Posts API, so 500 pages are 500 calls per pass of every account, plus what reading their figures adds. That is why the ceiling exists.

Usage

Importing what a page already published.

  • The Update button of the dashboard imports the publications of the pages. Without this module that button refreshes the daily series of the page and the figures of the publications of the last 30 days; with it, it also brings in the publications Odoo does not have yet and the figures of the ones older than that window.

  • The import discovers the publications three ways: one publication by its reference, the whole feed page by page, or — the ordinary path — the first page of the feed sorted by last modification, which is what catches what changed since the previous pass.

  • It also downloads the medias of the publications created outside of Odoo, so those images appear on the dashboard only after the import.

  • The publication mirrors what is online: an image removed from the post on LinkedIn is dropped from the dashboard card on the next import. Only the medias downloaded from LinkedIn are managed this way, so a file attached by hand in Odoo is never removed.

  • An import of the whole page leaves a mark with the figures of the last watched days. The bihourly check compares against that mark, which is why importing is what takes the notice down; refreshing a single publication takes it down too but leaves the mark alone, since one publication says nothing about the rest of the page.

  • The first import asks for the time series again on an account whose association could not read it, which is the retry of a backfill that failed and not a repetition of it. How far back the series goes is the connector’s business: Social Media Linkedin fills it as far back as LinkedIn answers by day the moment the account is linked, and rebuilds it from the Rebuild statistics history button of the account form.

Comments and reactions.

  • The comment thread of a publication is read from the dashboard, and its replies with it: LinkedIn serves the replies of a comment apart, so they are asked for when the thread is opened.

  • A comment and a reply are written under the organization, and the thread offers to delete a comment: LinkedIn is the one deciding whether the organization may, and its refusal is reported instead of removing it here.

  • Recommend is offered on the publication and on each of its comments, sent under the organization. LinkedIn holds one reaction per account and only LIKE is sent, so the entry is a toggle: it shows a filled thumb on what the organization already recommended, and pressing it there withdraws the reaction. What LinkedIn holds is read back on every import, so a reaction made or withdrawn from LinkedIn itself reaches the dashboard on the next pass.

  • LinkedIn does not accept an image in a comment, so the composer offers no upload on a LinkedIn thread.

Publications deleted on LinkedIn.

  • Posts deleted directly on LinkedIn are marked as Deleted on LinkedIn in Odoo, so the dashboard keeps their history. Only the Full resync button and the weekly scheduled action notice them, not the ordinary import, so a deletion can take up to a week to be reported.

  • Opening a publication asks LinkedIn first, so one deleted there is reported and marked right away instead of waiting for the weekly pass.

Full resync of a page

  • Go to Social Media > Configuration > Accounts, select the account and click on the Full resync button of the header.

  • The Update button of the dashboard reads one page of the feed, the one sorted by last modification, and refreshes the figures of every publication by its identifier. That is enough for everything except one thing: it cannot notice that a publication was deleted on LinkedIn, because a publication missing from the page it read is not necessarily gone.

  • Full resync is what reads the whole feed and marks as Deleted on LinkedIn what is no longer there. It is also the expensive pass, one call per hundred publications against the daily quota, so it asks for confirmation.

  • The scheduled action Social: Full resync of the accounts does the same weekly for every account, so pressing the button is only needed to see a deletion reported sooner.

  • A publication edited on LinkedIn long enough ago to have fallen off that first page keeps the text Odoo already had until a full resync. Its figures are refreshed all the same.

How the synchronization spends the LinkedIn quota

The throttle limits of LinkedIn are counted per day, per application and per endpoint, so what matters is not the total number of calls but how they spread.

  • The scheduled action checking for updates costs two calls per account and run, whatever the number of publications, one when it does find something, and only one for an account already announcing publications to import or for one whose credentials expired: the daily series of the page is written for them all the same, and what is skipped is the second call, the peek at the feed that could only confirm what the dashboard already says or fail on a token known to be dead. No call at all is spent on an account with no organization linked, since the finder is addressed by organization and there is nothing to ask. It reads the figures LinkedIn reports for the whole page day by day and compares them against the ones the last import left: no publication is read one by one to decide whether the dashboard should announce updates. Running every two hours, that is around 24 calls a day per account.

  • The Update button does not walk the feed. The statistics are asked for by publication identifier, and those identifiers are already stored in Odoo, so one page of the feed is enough: the one sorted by last modification, which is what brings the publications created or edited on LinkedIn. That is one call instead of one per hundred publications.

  • The figures of the publications are not read here: the import hands the identifiers to Social Media Linkedin, which is where that reading lives, so the same three calls answer the page whether they are asked for by this import or by the daily refresh of the connector.

  • Those figures are asked in as many calls as the 4 KB limit of the query string needs, since those endpoints take every identifier in the URL and none of them paginates. Around a hundred publications fit in one call, so a full feed takes several.

    https://learn.microsoft.com/en-us/linkedin/marketing/community-management/organizations/share-statistics

  • Full resync is the expensive pass, one call per hundred publications. The button does it for the account it is pressed on; the weekly scheduled action does it for every account of the database. It walks the feed page by page (count=100) up to as many pages as social_media_linkedin_sync.posts_max_pages allows, fifty by default, that is 5000 publications, and a page is only taken as the last one when it comes back empty, because LinkedIn documents that a page may carry fewer publications than asked while more are left. On an account whose feed is longer than that, the answer is incomplete and the sweep that marks as Deleted what is no longer on LinkedIn is skipped for that run: reporting nothing is preferable to marking a publication that is still online.

What the check for updates does and does not notice

  • It only tells that something on the page moved, not which publication moved. It does not need to: the only thing it decides is whether the dashboard shows the notice inviting to synchronize. A change that cancels itself out between two runs, a reaction removed and another one added, leaves the figures of the page where they were and goes unnoticed until the next synchronization.

  • It watches the daily figures of the page, over a window of the last seven days, and not the lifetime totals the same endpoint answers when no time interval is given. The two are two separate reads of LinkedIn’s own data, so they are not guaranteed to agree at every instant; this module only relies on the daily buckets. One consequence worth knowing: activity older than the seven-day window is not announced. It is imported all the same when the user synchronizes, because the import reads the publications themselves and not this window.

  • The engagement is not compared. It is a ratio of the clicks, reactions, comments and shares over the impressions, so it cannot move without one of those moving, and it is the only non-integer figure of the set.

  • The statistics endpoint only answers activity of the last 12 months, on a rolling window. A publication older than that stops being counted, which can move the figures of the page on its own, with nobody having interacted with anything. The check announces updates once when that happens and the next import reconciles it.

Known issues / Roadmap

One definition per symbol

The calls of this module cross towards Social Media Linkedin, never the other way around: _request_linkedin, _get_posts, _linkedin_statistics_values, _get_linkedin_daily_statistics, _refresh_post_statistics with the reading by URN behind it — _get_entity_statistics, _get_entity_share_statistics, _get_ugc_posts_statistics, _parse_share_statistics, _filter_urns — and the constants of social_linkedin_utils.py are asked for where the connector defines them. The import does not read the figures of a publication either: it hands the page it discovered, plus the publications the page did not bring, to _refresh_post_statistics and lets the connector spend the calls. Copying one of them here would be the failure that shows in nothing: the two copies drift apart with the first change, and the daily series and the import mark stop speaking the same language.

Where the connector needs something only this module knows how to do, it declares an empty hook — _linkedin_check_updates — and this module overrides it.

Where the daily series is filled

_backfill_statistics is the empty hook of Social Media Base and Social Media Linkedin implements it, so neither the method nor the calls it spends belong to this module. Base asks for it the moment an account is associated, the Rebuild statistics history button of the account form asks for it again with force=True, and the initial synchronization of Social Media Sync asks for it once more, which on an account that already holds its series is the retry of an association whose history could not be read rather than a repetition of it.

They are the figures of the whole page –what the finder answers without a list of URNs counts what was published before Odoo and outside of it– so what this module changes is when the series is asked for again, not what is being counted.

_snapshot_statistics, _linkedin_backfill_window and _STATISTICS_HISTORY_MONTHS_LINKEDIN stay in the connector, and so do the calls that use them: _backfill_statistics is the only caller of the three. This module asks for none of them.

Who wrote a comment

  • A person cannot be named. GET /v2/socialActions/{urn}/comments identifies the author of a comment by a URN and nothing else, and no public endpoint turns a urn:li:person: into a name: the Profile API answers for the authenticated member alone, r_1st_connections_profile was retired, and the token the connector holds belongs to the page and not to a person. The website shows the name because it reads endpoints of its own that are not exposed. Reading it needs the Partner Program, an access level of the application.

  • What the module does with that limit is resolve what can be resolved and say nothing where it cannot. The account itself is named from Odoo, an organization is read with GET /v2/organizations/{id}, and everything else is drawn as LinkedIn member or LinkedIn page. A comment is never signed with the name of the page that did not write it.

  • An organization the account does not administer may answer 403. The thread keeps loading, that comment falls back to the neutral label and the refusal is only logged: a name is not worth a thread.

  • The names are resolved once per thread and not kept. A cache of actors would save the calls a reopened dialog repeats, and it would need its own expiry and its own security; it is not implemented.

Media in the comments

  • A comment is published with its text alone: the connector sends actor, message and object, never content, and the comment composer offers no attachment for a LinkedIn publication.

  • The Comments API documents an example carrying media, and it is not what an ordinary application gets. Measured against a real page on 2026-08-24, with an image already uploaded through the Images API, the two ways of sending it answer:

    • POST /rest/socialActions/{urn}/comments — 403 ACCESS_DENIED, “Not enough permissions to access: partnerApiSocialActions.CREATE”. The versioned resource belongs to the Partner Program, which is an access level of the application and not something a paid plan buys.

    • POST /v2/socialActions/{urn}/comments, the endpoint the connector talks to, with the same payload plus content — 500 Internal Server Error, while the very same call without content publishes the comment.

  • So the media stays out until LinkedIn grants that access to the application: the endpoint that would take it is refused, and the one that answers does not accept the field.

The weekly full resync is not spread over the days

  • The Full resync button reconciles one account, the one it is pressed on. The scheduled action does not: it walks every account of the database in the same weekly run, each in its own savepoint, and that is the expensive pass, one call per hundred publications of each account.

  • On a database with several large pages that single run could exhaust the daily quota and make the manual refresh fail with it. Spreading the accounts over the days of the week is the way out and is not implemented.

Asking LinkedIn only for what changed

  • It is not possible with these endpoints, so the statistics cost one call per batch of identifiers whatever moved:

    • The /posts finder takes author, start, count, sortBy and viewContext. There is no since of any kind, so there is no equivalent to what the X connector does with since_id.

    • The statistics of specific publications cannot be restricted to a period: “Time-bound statistics is not supported for specific share queries”.

    • The daily figures of the page say that something moved, not which publication moved.

  • There is a stream of engagement events, and it is not used. It would tell which publication changed, and with it the refresh could ask about that one alone. It is not implemented because of three limits worth weighing first: it only reports reactions, comments and reshares, never impressions or clicks, which would still need the ordinary refresh; the publication it points at comes as an urn:li:activity: and only the webhook payload carries the matching urn:li:share:, the pull finder does not, so pulling alone cannot attribute an event to a publication; and the webhook needs a publicly reachable HTTPS URL validated by LinkedIn plus a subscription per member and organization. The permission it needs, rw_organization_admin, is already requested by Social Media Linkedin.

    https://learn.microsoft.com/en-us/linkedin/marketing/community-management/organizations/organization-social-action-notifications

Reactions other than Like

  • Only LIKE is sent, both on a publication and on a comment. The Reactions API also takes PRAISE, EMPATHY, INTEREST, APPRECIATION and ENTERTAINMENT. Nothing on the LinkedIn side holds them back — the same endpoint and the same permissions serve them — so offering them is a matter of choosing the reaction on the dashboard, and of drawing an entry that is no longer one thumb with two states.

Video of a publication

  • The video of a published post is not attached to the publication imported from LinkedIn: only the has video flag is kept. Bringing the file over means downloading it from LinkedIn on every synchronization, which is not implemented.

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.

Metadata

Release files for odoo-addon-social-media-linkedin-sync 17.0.1.0.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for odoo-addon-social-media-linkedin-sync 17.0.1.0.0.3
File Interpreter ABI Platform
odoo_addon_social_media_linkedin_sync-17.0.1.0.0.3-py3-none-any.whl Python 3 none any Details

Release files / odoo_addon_social_media_linkedin_sync-17.0.1.0.0.3-py3-none-any.whl

Download URL odoo_addon_social_media_linkedin_sync-17.0.1.0.0.3-py3-none-any.whl
Size 119.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ac8d42b5d80c29fa0eab2d988cb9b33aa94a4dc8f038497bb711e17cc75364c2
BLAKE2b-256 checksum
How to use checksums
3dc2727cd9c283d88e80a7eec86ec4f0f8448d1dcab2ac60d05a6cdf4520c5f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release history Release notifications | RSS feed

This release

17.0.1.0.0.3 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page