Overview
Velt supports self-hosting your activity log PII data:- Activity content (comment text embedded in change history), feature-specific entity snapshots, and custom fields can be stored on your own infrastructure, with only necessary identifiers on Velt servers.
- Velt components automatically re-hydrate activity data in the frontend by fetching from your configured data provider.
- This gives you full control over PII while maintaining all Velt activity log features.
How does it work?
When activity records are created or read:- The SDK uses your configured
ActivityAnnotationDataProviderto handle storage and retrieval. - Your data provider implements two optional methods, each of which can be supplied as either a callback function or a config endpoint URL:
get/getConfig: Fetches activity PII from your database and returns it for re-hydrationsave/saveConfig: Stores PII fields stripped from the activity record before Velt writes to its backend
- Each method is considered valid as long as it has either a callback function (
get/save) or a corresponding config endpoint URL (getConfig/saveConfig).
stripActivityPII):
- The SDK strips PII from the activity record. How much is stripped depends on
featureType— see What gets stripped below. - Your
savehandler receives the stripped PII (PartialActivityRecord) and stores it in your backend. - Velt’s backend stores the skeleton record (structural identifiers, timestamps, flags) plus any non-PII fields.
rehydrateActivities):
- The SDK fetches the activity records from Velt’s backend (with PII absent).
- The SDK runs 5 resolver passes in order:
user→comment→reaction→recorder→activity. Yourgethandler is called during the activity pass with the activity IDs. - The returned PII is merged back into the activity records before they are delivered to your UI.
ActivityRecord.isActivityResolverUsedis set totruewhen PII was stripped by the resolver. Use this to show a loading state while re-hydration is pending.
What gets stripped
How much PII is removed fromentityData and entityTargetData depends on featureType. On top of the automatic behavior below, config.fieldsToRemove applies to all activity feature types (comment, reaction, recorder, and custom) — any top-level keys you list are moved wholesale to your DB and restored on read.
Built-in featureType (comment / reaction / recorder) — partial strip
entityData and entityTargetData objects are kept on the Velt record; only specific PII fields inside them are removed. Structural identifiers (annotation IDs, comment IDs, target IDs) are preserved so the read path can re-match resolved data.
changes['commentText'] is moved to your DB only when the activity resolver is active. If only a comment resolver is registered (no activity resolver), it is preserved on the Velt side to avoid unrestorable loss.
Comment entityData / entityTargetData PII is handled by the comment resolver’s own store, not duplicated through the activity resolver.
Any top-level keys you additionally list in config.fieldsToRemove are moved wholesale to your DB on top of this automatic strip.
featureType === 'custom' — wholesale removal by config
There is no automatic field-level stripping for custom activities. The only fields removed are the top-level keys you list in config.fieldsToRemove — each listed key is moved wholesale to your DB and deleted from the Velt record. On read it’s restored by wholesale replacement.
- If you list
entityDataorentityTargetDatainfieldsToRemove, the entire field disappears from the Velt record (not field-by-field). - If you don’t list them, they’re left untouched on the Velt record.
Universal
displayMessageis always recomputed on the client and stored in neither DB.actionUserand any user objects inchangesanddisplayMessageTemplateDataare reduced to{ userId }whenever auserresolver is active.- Activity is append-only — there is no delete.
Implementation
Implementation Approaches
You can implement activity self-hosting using either of these approaches:- Endpoint based: Provide endpoint URLs and let the SDK handle HTTP requests
- Function based: Implement
getandsavemethods yourself
Endpoint based DataProvider
Instead of implementing custom methods, you can configure endpoints directly and let the SDK handle HTTP requests.headers may also be an async function resolved per request, and credentials ('include' | 'same-origin' | 'omit') enables cookie/session auth — see Async headers and credentials for details.Activity is append-only — there is no
delete operation, so there is no deleteConfig for activity.getConfig
Config-based endpoint for fetching activity PII. The SDK automatically makes HTTP POST requests with the request body.- Type:
ResolverEndpointConfig - Request body format:
GetActivityResolverRequest - Response format:
ResolverResponse<Record<string, PartialActivityRecord>>
- React / Next.js
- Other Frameworks
saveConfig
Config-based endpoint for saving stripped activity PII. The SDK automatically makes HTTP POST requests with the request body.- Type:
ResolverEndpointConfig - Request body format:
SaveActivityResolverRequest(the SDK sends{ activity, event, metadata }) - Response format:
ResolverResponse
- React / Next.js
- Other Frameworks
Endpoint based Complete Example
- React / Next.js
- Other Frameworks
Function based DataProvider
Implement customget and save methods to handle data operations yourself.
get
Fetch activity PII from your database. Called when activity records need to be re-hydrated in the frontend.- Param:
GetActivityResolverRequest - Return:
Promise<ResolverResponse<Record<string, PartialActivityRecord>>>
- React / Next.js
- Other Frameworks
save
Store activity PII fields stripped before Velt writes to its backend. Called when an activity record is created or updated.- Param:
SaveActivityResolverRequest - Return:
Promise<ResolverResponse<undefined>>
- React / Next.js
- Other Frameworks
config
Configuration for the activity data provider.- Type:
ResolverConfig. Relevant properties:resolveTimeout: Timeout duration (in milliseconds) for resolver operations.getRetryConfig:RetryConfig. Configure retry behavior forgetoperations.saveRetryConfig:RetryConfig. Configure retry behavior forsaveoperations.RetryConfigis{ retryCount?: number; retryDelay?: number; revertOnFailure?: boolean }. Note:revertOnFailurereverts the optimistic cache update if the save ultimately fails.getConfig:ResolverEndpointConfig. Endpoint URL + headers for fetching activity PII. See Endpoint based DataProvider.saveConfig:ResolverEndpointConfig. Endpoint URL + headers for saving stripped activity PII. See Endpoint based DataProvider.fieldsToRemove:string[]. Top-level keys to move wholesale from the activity record to your DB. Applies to all activity feature types (comment,reaction,recorder, andcustom). For built-in feature types it runs in addition to the feature-aware partial strip described in What gets stripped; forcustomit is the only stripping that happens. ListingentityDataorentityTargetDatamoves the entire field (not field-by-field). Fields are matched on!== undefined, so falsy-but-meaningful values (0,false,"") are moved too.
Function based Complete Example
- React / Next.js
- Other Frameworks
Loading State
UseisActivityResolverUsed on ActivityRecord to show a loading state while PII is being fetched from your backend:
Sample Data
- Stored on your database
- Stored on Velt servers

