Written for the person filling in a security questionnaire. It is generated from the code's own retention rules, so it cannot quietly drift from what the software actually does.
Everything that processes customer data. Your own client can object to any of these, so the list is complete rather than convenient. We give 30 days' notice before adding one.
| Who | What for | Where | What they can see |
|---|---|---|---|
| LinkedIn (Microsoft) | publishing, and the analytics read back | global | the posts you publish and the pages you connect |
| Contabo GmbH | runs the application and the database | Germany (European Union) | everything, encrypted at rest |
| Google Workspace (Google Ireland Ltd) | invitations and notifications | global | recipient addresses and message contents |
These are the values the code enforces, not a policy written
beside it. Deletion runs on a schedule; /api/retention shows
whether anything is overdue, and it is readable by any signed-in customer.
| What | Kept | Why that long |
|---|---|---|
| login_challenges | 24 hours | a five-minute note saying a password was right and a code is still needed. Spent on use, and the row deleted within a day either way |
| li_actors | 24 hours | other members' profile data — cache only |
| resets | 24 hours | password reset links — a way into an account, held for hours and deleted within a day |
| credential_calls | 2 days | one row per call made on a credential, which is how a ceiling that belongs to the credential rather than to the installation is counted. The window is a minute; two days is kept so a burst can still be explained the morning after, and past that the row has no reader |
| sessions | 2 weeks | where each person is signed in — the address and browser a session was started from, so somebody can see a session they do not recognise and end it. Swept well past the point where the session itself has expired |
| li_comments | 6 weeks | organization social activity |
| li_mentions | 6 weeks | organization social activity |
| reply_drafts | 6 weeks | replies drafted to comments on this account's own posts. Swept on the same clock as the comments they answer, because a reply to a comment nobody can read any more is text with nothing on the other side of it |
| sentiment_reads | 6 weeks | how the comments on a post were read, as counts and the comments behind them. Swept on the same clock as the comments themselves, because a verdict whose evidence has aged out is a bare number |
| li_deliveries | 2 months | webhook de-duplication, our own |
| deliveries_out | 3 months | every attempt to tell another system what happened, and what was sent |
| sandbox_mail | 3 months | mail a sandbox account produced, captured here instead of being delivered. It carries the addresses somebody typed into a demonstration, which are usually real people's, so it is swept on a clock like any other personal data rather than kept because it is only a demonstration |
| li_stats | 1 year | page admin and reporting data |
| li_member_stats | 1 year | the authenticated member's own data |
| checkins | 1 year | which licensed installations have said they exist: a licence id, an installation id, a build, two counts and the address it arrived from. One row per installation, kept for a licence year and its grace, because a dispute about a term is argued after the term |
| enquiries | 2 years | the client's own enquirers, in their own words — their commercial record, kept two years so a year-on-year comparison is possible, then gone |
| voice_vetoes | 2 years | a person saying a draft did not sound like them, with their name. Two years: after that the record it fed into is the thing that matters, not the objection |
| sign_offs | 7 years | who approved what, with the address it came from. Seven years, because the question an approval answers is asked long after the fact and a regulated client's retention obligation is usually six |
| adv_items | — | posts and links offered to the brand's people; deleted with the account. |
| adv_people | — | names and emails of the brand's people who share its posts; removed when taken off the list or with the account. |
| adv_sends | — | who was asked, whether they opened it and which share buttons they pressed; deleted with the account. |
| ai_assisted | — | which posts a model helped write, which part of them, and whether a person edited it afterwards. Ours, about our own copy. It must outlive nothing in particular except the post it labels: a swept row turns a labelled post into an unlabelled one, which is the one failure the label exists to prevent. |
| ai_consents | — | who agreed that this account may have a model write drafts, study how they write, or draft replies — and the exact wording they were shown when they agreed. Kept for the life of the account and deliberately not swept: a consent record that ages out leaves the account looking as though it never agreed, and a revocation that ages out leaves it looking as though it never withdrew. Both are the record somebody would be asked to produce. A name and an address are in it, and they go with the account. |
| ai_reports | — | somebody's report that the software produced something harmful, and what was done about it. A name and an address where the reporter gave them. Kept for the life of the account because the pattern across reports is the thing worth seeing, and a clock would delete the early ones — which are the ones that would have shown it. It goes when the account does. |
| api_tokens | — | keys an agency issued to its own tooling: a label, the scopes, who made it and when it was last used. Only the hash of each token is here, so this table is not a set of working keys. Kept for the life of the account because a revoked key's row is the record that answers 'what was this reaching, and when did it stop' — and a row swept on a clock takes that answer with it. It goes when the account does. |
| app_keys | — | Dispatch's own app ids and sealed secrets on other platforms; operator data, not a customer's. |
| av_advice | — | advice given and whether it helped; deleted with the account. |
| av_keys | — | Perplexity and OpenRouter keys a brand added for AI visibility, sealed; deleted with the account or when removed. |
| av_questions | — | the questions a brand tracks in AI search; deleted with the account. |
| av_runs | — | answers and cited sources from AI search for those questions; deleted with the account. |
| av_settings | — | names, profile and competitors a brand tracks in AI search; deleted with the account. |
| bio_clicks | — | clicks per link per day on that page, with no visitor details; deleted with the account. |
| bio_pages | — | a brand's public link-in-bio page; deleted with the account. |
| canva_pkce | — | a sign-in in progress, kept for fifteen minutes at most. |
| canva_tokens | — | a sealed Canva connection; deleted on disconnect or with the account. |
| claims | — | which figure in which post rests on which source. Ours, about our own posts, and the thing the report cites. |
| context | — | what is true about the client, in their own words, and who confirmed each of it. A name and a timestamp are stored because 'confirmed' with nobody attached answers nothing. Deliberately kept for the life of the account: it is the record every other feature reads, and a clock that quietly deleted a confirmation would leave the entry looking unconfirmed rather than gone. It goes when the account does. |
| credentials_v2 | — | which accounts hold which kind of credential, from which vendor, when it was tested and what the vendor said. The secret itself is sealed and is erased the moment a credential is removed; the row stays so that 'when did this account stop having a key' has an answer, which is the first question asked when drafting stops. No personal data beyond the address of whoever added it, and it goes with the account. |
| draft_provenance | — | what one draft rested on when it was written: which confirmed entries, which sources and their state at the time, which model and purpose, how many tokens, and which person asked. Kept for the life of the account, because the question it answers is asked long after the post — 'where did this claim come from' is what somebody asks when a figure turns out to be wrong, and a row swept on a clock leaves the post standing with nothing behind it. A user id and an address are in it and they go with the account. |
| expectations | — | what was agreed with a client and when. A name is stored because 'agreed with somebody' answers nothing, and it is deleted with the account. |
| gov_approvals | — | who approved which post at which risk; kept with the post. |
| gov_facts | — | the claim ledger: facts, sources and expiry dates; deleted with the account. |
| gov_freeze | — | whether a brand's publishing is frozen; removed when released. |
| gov_policy | — | a brand's governance rules; deleted with the account. |
| hooks_out | — | where this account asked to be told about things, and which things. The signing secret is sealed rather than hashed, because signing needs it back. Configuration the account wrote itself, and a subscription that aged out would stop telling somebody without anybody being told it had. |
| ideas | — | a brand's ideas board; deleted with the account. |
| ideas_dismissed | — | suggestions someone dismissed, so they do not return; deleted with the account. |
| inbox_checks | — | when each post's comments were last read. |
| inbox_items | — | comments on the brand's own posts and the replies sent; deleted with the account. |
| intel_alerts | — | warnings raised for a brand (a rise in negative mentions, AI citations heading down); deleted with the account. |
| intel_settings | — | whether the brand's AI key reads comments; deleted with the account. |
| li_cursor | — | our own bookkeeping — where each pull got to. No LinkedIn data: timestamps and URNs we already hold, and a URN may be kept without limit. |
| li_hooks | — | our own webhook registrations: an organization URN, an event name and our callback URL. A URN may be kept without limit, and a registration that expired silently is deleted when the destination is disconnected, not on a clock. |
| li_import_daily | — | daily totals a member exported from LinkedIn about their own content: impressions, engagements, new followers. No personal data of anybody else; deleted with the account. |
| li_import_meta | — | when the export was imported, its period, and audience shares as top categories with percentages; deleted with the account. |
| li_import_posts | — | the member's own top posts from that export: a post URL, a date and two counts; deleted with the account. |
| linkedin_apps | — | an account's own LinkedIn application: a client ID and its sealed secret. Kept while the account uses it and deleted with the account. |
| listen_hits | — | public mentions found for those terms, with where, who, what and a link; deleted with the account. |
| listen_settings | — | whether X is searched; deleted with the account. |
| listen_terms | — | names and phrases a brand listens for; deleted with the account. |
| meta_requests | — | deletion and deauthorize requests from Meta, with the app-scoped id Meta sent; kept as the record that they were honoured. |
| model_spend | — | which model answered which purpose for which account and which person, and how many tokens it took. Deliberately not the brief or the answer: keeping those would put a customer's unpublished positions in a table that outlives the draft. Kept for the life of the account because it is what a customer checks their vendor's own invoice against. |
| net_pending | — | sealed choices after a network sign-in, until one is picked. |
| net_state | — | a network sign-in in progress; removed when it returns. |
| outbound_drafts | — | messages this account drafted to one person at a time. The account's own writing and nothing else: there is no column here for a name, a company, a title or an address, and what the message was about was used to write it and discarded. Kept with the account because it is the corpus the repetition check reads — the thing that says an opening has already gone to eleven people. |
| pay_events | — | gateway webhook ids already handled, so none is applied twice. |
| pay_plans | — | the plans created at Stripe and Razorpay; operator data. |
| pay_subs | — | which gateway subscription belongs to which account; kept with the account's billing records. |
| paypal_events | — | the IDs of PayPal events already handled, so a repeated delivery is not applied twice. An ID and a type. |
| paypal_plans | — | the PayPal plan ID for each plan and billing period. No personal data. |
| paypal_subs | — | which PayPal subscription belongs to which account, its plan and status. Kept while the subscription exists for billing. |
| pipeline_items | — | what to write about next, with why each item is there and what it would rest on. The account's own work product — a client's request, a gap somebody noticed, an obligation created by a source that moved. Kept for the life of the account because a queue that quietly empties is worse than an empty one: the item that ages out is the one nobody got to, which is exactly the one worth still seeing. It goes when the account does. |
| policy_agreements | — | who agreed to which version of which public policy, when, where and from which address. The proof of agreement: rows are only ever added, and they are deleted with the account. |
| post_metrics | — | likes, comments, shares and views each network reported for a published post; deleted with the account. |
| post_series | — | which posts somebody declared part of a named series, so the repetition guard can tell a deliberate repetition from an accidental one. Our own bookkeeping about our own posts, and a row that aged out would turn a deliberate series into a stream of false alarms. |
| post_versions | — | every version of our own copy, with who changed it. Our work product, and the answer to what a post said when it was approved. |
| pr_reviews | — | profile review results (scores and suggestions; the profile text is not kept); deleted with the account. |
| profile_text | — | the profile text somebody pasted in to have it checked — a headline, an About section — with who pasted it and when it was last looked at. Their own words about their own business, and this product cannot read the real thing from the network, so a clock that deleted it would silently stop watching a published claim rather than tidying anything up. It goes when the destination or the account does. |
| proposals | — | what we recommended and what the client decided. The record that answers 'you never brought us ideas'. |
| ps_fixes | — | corrections requested and whether they were made; deleted with the account. |
| ps_pages | — | public pages about the person with a short excerpt, private details removed; deleted with the account. |
| ps_scans | — | when each presence scan ran and what search showed of the LinkedIn profile; deleted with the account. |
| ps_settings | — | kind of work, role, location and the consent to scan; deleted with the account. |
| purpose_models | — | which model an account chose for each of the four jobs. Configuration the account wrote itself, and deleting it on a clock would silently move them back to the deployment default mid-month. |
| reading | — | a list of pages somebody meant to read: an address, a note, and what was found on it. The same shape as `sources` and governed the same way — a public web address and our own bookkeeping about it, with no personal data in it. Kept while the account is, because a reading list swept on a clock is a reading list that quietly empties between the Tuesday somebody adds a report and the week they need a figure out of it, which is the exact gap it exists to close. |
| recovery_codes | — | hashes of the one-time codes that get somebody back in when the phone is lost. Never readable, deleted with the account. |
| recs | — | actions suggested to a brand, what was done, and whether it helped; deleted with the account. |
| recs_weights | — | how much each kind of action has helped this brand; deleted with the account. |
| repeat_exempt | — | passages this account declared may reappear word for word — a quotation, a definition, a regulatory disclaimer — with who allowed each and why. Kept for the life of the account: an exemption that expired on a clock would start flagging a disclaimer that is mandatory, which is the false positive that gets a guard switched off. |
| review_checks | — | when each location's reviews were last read. |
| reviews | — | public Google reviews of the business and the replies sent; deleted with the account. |
| sandbox | — | which accounts are sandboxes, whose clock is offset by how much, and which failure was asked for next. Configuration about an account rather than anybody's data, and it must not be swept on a clock: a sandbox account whose row aged out would quietly start publishing to the real network, which is the one outcome this whole module exists to prevent. It goes when the account does. |
| signals | — | mood, topic and intent read from comments, mentions and reviews, and people's corrections; deleted with the account. |
| sources | — | a web address, a publisher, a date and a sentence quoted from a public page. No personal data, and deleting it would break the citation on a post that is still live. |
| two_factor | — | the sealed key behind somebody's authenticator app. Kept while they have one and deleted with the account. |
| utm_settings | — | whether a brand tags its links, and the campaign name; deleted with the account. |
| voice_profiles | — | what the software counted about how this account writes: sentence lengths, how posts open, which words recur, and the posts each of those was measured from. Measurements over the account's own copy, kept for the life of the account because the thing it is useful for is comparison over time — a profile swept on a clock would quietly reset to 'not enough posts' and take the account back to its cold start. It goes when the account does. |
| voice_records | — | how a brand argues, what it refuses to say, what is off limits. Configuration, written by the account itself. |
| webhook_refusals | — | how many deliveries to the public webhook URL failed to verify, per account — one row each, so a stranger posting junk at it cannot grow the database. An account id and a count. |
Two consequences worth stating. A comment and the person who wrote it have different clocks — the comment is organization activity at six weeks, the commenter's name is another member's profile data at twenty-four hours — so after a day a comment is still there and its author reads as "A LinkedIn member". And LinkedIn's terms do not permit member data to leave the application, including to you: client reports are built from aggregates, and the export path refuses to render a structure containing a name.
Each of these is something the test suite exercises, not an intention.
Sealed before they reach the database and authenticated on read, so a tampered row fails loudly rather than decrypting to nonsense. A stolen copy of the database is not a stolen set of LinkedIn accounts.
Salted per user, n=2^14. Not recoverable by us. A wrong password and a missing account take the same time, so neither can be told from the other.
Every account's reach is computed by a single function and every route asks it. The test suite enumerates every route the application exposes and attacks each one with an identity that must not reach it — a route added later without a check fails without anyone remembering to test it.
An account you may not see and an account that does not exist return the same status and the same words.
Nothing in the system updates or deletes an audit row. Who approved, connected, changed or erased what, and from which address.
On the address and on the network address separately, so one attacker cannot lock every account out and a botnet cannot walk through.
A session cookie that is HttpOnly and SameSite=Lax, and an origin check on every state-changing request.
A row filtered out of a query is still a row in the file. The retention sweep deletes, and /api/retention shows whether anything is overdue.
As a processor to you, and a sub-processor where you are acting for your own client.
| Categories of data subject | Staff of the agency; staff of the agency's client brands; LinkedIn members who comment on or mention a published post |
| Categories of personal data | Name, business email address, role, hashed password, last sign-in; authored post content; LinkedIn access tokens; for commenters, the name, headline and photograph LinkedIn returns |
| Special category data | None. Do not put any into a post. |
| Purpose | Scheduling and publishing content to LinkedIn on the controller's documented instruction, and reporting on it |
| Recipients | LinkedIn; the sub-processors listed above; nobody else |
| Transfers | Standard contractual clauses, Module Two, incorporated in the annex to the data processing addendum at /dpa, with the UK Addendum and the Swiss adaptations |
| Retention | As set out in the table above. Erasure is a button and it deletes, including files on disk |
| Security | As set out below |
A trust page that only lists strengths is a trust page nobody believes. These are the gaps, and when each would be worth closing.
None is held today. For UK and EU buyers Cyber Essentials Plus is the usual ask and is days rather than months; it is the one we would pursue first, at around ten customers. SOC 2 Type II is 12–18 months end to end and worth starting only when a named deal depends on it.
There is no measured operating history to commit against, and a service credit is cheap while a broken promise is not. This will be offered once there is a quarter of real numbers behind it.
Not yet commissioned. The isolation suite is adversarial and mutation-tested, which is not the same thing and is not offered as if it were.
The standard contractual clauses are in force and are in the annex to the addendum. The written assessment of Indian law that is expected to sit beside them since Schrems II is not yet done. A buyer who asks for one should be told that rather than handed something improvised.
Everything is written from LinkedIn's documentation and proven against a test double. The Standard tier application has not yet been made.
Every page is checked against WCAG 2.2 level AA by an automated suite that opens it in a browser, walks every element that draws text, and compares the colour actually painted against the background actually behind it. It runs on both the light and the dark theme, on the public pages and inside the signed-in panels, and it covers dialogs, error messages, toasts and focus rings — the states a screenshot review never sees. The same suite opens every page at eleven widths from a 320-pixel phone to a 2560-pixel monitor, and at 200% zoom, and fails if the page scrolls sideways or if a control is smaller than 24 by 24 pixels.
What that does not mean: no automated check can tell you a page is usable with a screen reader, and this product has not been tested with one, or with users who rely on assistive technology. Saying “WCAG 2.2 AA compliant” would overstate it. If something here does not work for you, write to the address below — we would rather hear about it than not.
Two things are kept in the browser. A cookie called session,
which is what keeps somebody signed in — HttpOnly, SameSite=Lax, Secure,
and it lasts as long as the session does. And a theme preference called
dispatch.theme in local storage, written only when the reader
presses the light/dark switch, never sent to us. Both are exempt from consent:
one is strictly necessary, the other exists only because the reader asked for
it. There are no analytics cookies, no advertising cookies and no tracking
scripts, which is why there is no cookie banner. The full description is in the
privacy notice.
No request leaves the page. Every file a page needs — its markup, its script, its typefaces — comes from this installation and nowhere else. The content policy this server sends names no outside origin at all, so a browser would refuse one even if a page asked. Nothing about a visit reaches a third party, and nothing here is a tracker.
This paragraph used to say the opposite, and say so plainly: the typefaces came from Google Fonts, which discloses the requesting address and browser to Google, and it ended “we intend to serve the application’s fonts ourselves too”. That is now done. The faces are served from this installation, with their licences and a record of where they came from beside them. We have left the history here rather than quietly rewriting it, because a trust page that only ever describes the present is one you cannot tell has changed.
We will tell affected accounts and, where required, the supervisory authority without undue delay and in any event within 72 hours of becoming aware, saying what we know even where that is not yet everything. In practice we will tell you as soon as we can rather than waiting for the deadline: your own 72 hours to a regulator starts when you learn of it, not when we do.
Security questionnaires: send CAIQ Lite or SIG Lite to security@xonik.com. We will acknowledge it within two working days and tell you when to expect it back. Data protection: privacy@xonik.com. The data processing addendum is at /dpa.
Dispatch calls no language model. It holds no model provider's key and no code path sends an account's data to one, which is why no model provider is listed as a sub-processor above. Drafting is done in an assistant on the operator's own side, which uses the tools Dispatch publishes to put a draft into the queue; work travels inwards and a person approves it before anything publishes. The privacy notice describes this in full.
Assembled with the help of a language model. Check each figure against the source shown beside it before acting on it.