Enable public access to your community with visitor mode. Allow anonymous users to discover content while maintaining platform integrity through server-side read-only controls.
Visitor mode enables anonymous public access to your community, allowing users to browse and discover content without requiring authentication. This feature is ideal for growth funnels, SEO optimization, and public content discovery while maintaining platform security and stability.
Visitor Users
Purpose: Anonymous users who browse public content Access: Read-only permissions enforced server-side Tracking: Identified by device fingerprinting Use Case: Public content discovery, growth funnel
Bot Users
Purpose: Search engine crawlers and automated indexers Access: Read-only permissions for content indexing Tracking: Identified by User-Agent analysis Use Case: SEO optimization, content discoverability
Why Device Fingerprinting? This approach allows tracking unique anonymous visitors for analytics while maintaining privacy. Visitors are restricted to read-only access, protecting community integrity.
Verify the current user type to adapt your UI accordingly:
let userType = client.currentUserTypeswitch userType {case .signedIn: print("User is authenticated")case .visitor: print("User is a visitor")case .bot: print("User is a bot")}
For production environments, secure visitor mode adds an extra layer of authentication by requiring cryptographic signatures for visitor sessions. Once secure mode is enabled, all visitor login requests must include a valid auth signature generated by your backend server.
Secure visitor mode is not enabled by default. Even if visitor mode is enabled, secure mode must be enabled separately. Contact [email protected] to enable secure visitor mode for your network.
After visitor secure mode is enabled for your network, retrieve your visitor application secret from the Console:
1
Navigate to Settings
Open your social.plus Console and go to Settings → Integrations
2
Locate Visitor Secret
Scroll to the Visitor Secure Mode Setup section (visible only after visitor secure mode is enabled)
3
Copy Secret
Create new secret and store it securely in your backend environment variables
Security Best Practice: Never expose your secret in client-side code, mobile apps, or version control. This secret must remain on your backend server only.
How It Works: The signature is created by hashing the device ID and expiration timestamp with your secret key. social.plus servers verify the signature using the same secret, ensuring the request originated from your trusted backend.
Use auth signatures for production visitor sessions:
Obtain authSignature via the API implemented in the previous step, and provide the corresponding values to loginAsVisitory() function.
Task { @MainActor in do { // Get device ID let deviceId = client.getVisitorDeviceId() // Request auth signature from your backend let (signature, expiresAt) = try await fetchAuthSignature(deviceId: deviceId) // Login with secure mode try await client.loginAsVisitor( authSignature: signature, authSignatureExpiresAt: expiresAt, sessionHandler: visitorSessionHandler ) print("Secure visitor login successful") } catch { print("Secure visitor login failed: \(error)") }}
Implement session handlers to automatically refresh auth signatures:
iOS
Android
TypeScript
class VisitorSessionHandler: AmitySessionHandler { func sessionWillRenewAccessToken(renewal: AccessTokenRenewal) { let deviceId = client.getVisitorDeviceId() // Fetch new auth signature from your backend AuthService.shared.fetchVisitorAuthSignature(deviceId: deviceId) { result in switch result { case .success(let authData): renewal.renewWithAuthSignature( authSignature: authData.signature, authSignatureExpiresAt: authData.expiresAt ) case .failure(let error): print("Failed to refresh visitor token: \(error)") renewal.unableToRetrieveAuthSignature() } } }}// Use during visitor loginlet sessionHandler = VisitorSessionHandler()try await client.loginAsVisitor( authSignature: signature, authSignatureExpiresAt: expiresAt, sessionHandler: sessionHandler)
class VisitorSessionHandler : SessionHandler { override fun sessionWillRenewAccessToken(renewal: AccessTokenRenewal) { val deviceId = AmityCoreClient.getVisitorDeviceId() // Fetch new auth signature from your backend authRepository.fetchVisitorAuthSignature(deviceId) { authData -> if (authData != null) { renewal.renewWithAuthSignature( authData.signature, authData.expiresAt ) } else { renewal.unableToRetrieveAuthSignature() } } }}// Use during visitor loginval sessionHandler = VisitorSessionHandler()AmityCoreClient.loginAsVisitor(object : SessionHandler { override fun sessionWillRenewAccessToken(renewal: AccessTokenRenewal) { renewal.renew() } }) .authSignature(signature) .authSignatureExpiresAt(expiresAt) .build() .submit()
Visitors and bots are excluded from resource-intensive features:
Real-Time Events
Push Notifications
User Discovery
MQTT Connection: Disabled for visitors/bots
No real-time event subscriptions
No live updates or notifications
Reduces server load and connection costs
Does not count towards CCU (Concurrent Connection Users) limits
// SDK automatically skips MQTT connection for visitorsconst userType = Client.getCurrentUserType();if (userType === UserTypeEnum.VISITOR || userType === UserTypeEnum.BOT) { // mqtt.connect() is NOT called}
Push Notifications: Blocked for visitors/bots
Cannot register device tokens
Filtered out from notification recipient lists
Applies to all notification types
Prevents unpredictable costs from anonymous audience
// Push notification registration is blockedif (userType === AmityUserType.VISITOR || userType === AmityUserType.BOT) { // registerPushNotification() throws error or no-ops}
User Listing/Search: Hidden from results
Excluded from user search APIs
Not visible in followers/following lists
Hidden from user discovery features
Maintains authentic member directories
// Visitors are automatically filtered from user queries// Your queries return only signed-in usersconst users = await UserRepository.searchUserByDisplayName({ displayName: 'John' });// Returns: only SIGNED_IN users, no VISITOR or BOT users
AmityUIKit4Manager.behavior.globalBehavior = object: AmityGlobalBehavior() { override fun handleVisitorUserAction() { // Custom behavior for visitor user action } override fun handleNonMemberAction() { // Custom behavior for non-member action } override fun handleNonFollowerAction() { // Custom behavior for non-follower action }}
// AmityGlobalBehavior - Default visitor interaction handlingconst { AmityGlobalBehavior } = usePageBehavior();// Visitor userAmityGlobalBehavior.handleGuestUserAction();// Non-Member user in a communityAmityGlobalBehavior.handleNonMemberAction();// Non-Follower user of a userAmityGlobalBehavior.handleNonFollowerAction();
To prevent accumulation of transient visitor data, social.plus automatically cleans up inactive guest users:
Automatic Cleanup Policy
Schedule: Periodic cleanup (configurable, typically 30-60 days)Criteria: Guest users inactive for the defined periodProcess:
Scheduled job runs automatically
Identifies inactive guest user records
Permanently deletes inactive guest data
No manual intervention required
What’s Deleted:
Guest user profile records
Device fingerprint associations
Session history
Any cached visitor data
Data Retention Considerations
Active Visitors: Continuously using visitors retain their dataPrivacy Compliance: Automatic cleanup supports GDPR/privacy regulationsAnalytics Impact: Historical analytics remain unaffectedConversion Tracking: Converted visitors (who signed up) preserve their history
Event Availability: Guest user events are available through existing webhook/event observation mechanisms configured in your social.plus console.
// Example: Strategic upgrade promptsconst handleVisitorAction = (action: string) => { const userType = client.getCurrentUserType(); if (userType === UserTypeEnum.VISITOR) { showUpgradePrompt({ action, message: getContextualMessage(action), benefits: [ 'Join the conversation', 'Create and share content', 'Connect with community members', 'Get personalized notifications' ] }); }};const getContextualMessage = (action: string) => { const messages = { 'react': 'Sign up to react and engage with posts', 'comment': 'Create an account to join the discussion', 'follow': 'Sign in to follow users and communities', 'post': 'Become a member to share your thoughts' }; return messages[action] || 'Sign up to unlock all features';};