Instances

Integrate Instances

This guide walks you through integrating CashIn's Instance endpoints into your application. Instance endpoints manage user sessions, track campaign participation, and handle state transitions from anonymous browsing to authenticated experiences.

Instance Endpoints

Endpoint

Method

Purpose

Key Parameters

Response Data

/partner/v1/instance/anonymous

POST

Create temporary instance for anonymous users

fingerprint_id, campaign_id, insider_id

instance_id, partner_id, expires_at

/partner/v1/instance/validate

POST

Check if instance is valid and active

instance_id

valid, instance object, reason

/partner/v1/instance/extend

POST

Extend expiration time of active instance

instance_id

new_expires_at, extended_by_hours

/partner/v1/instance/revoke

POST

Immediately invalidate an instance

instance_id

revoked_at

Authentication Setup

All Instance endpoints require Bearer token authentication using your partner secret key.

const headers = {
  'Authorization': 'Bearer sk_live_abc123def456...',
  'Content-Type': 'application/json',
  'X-Request-ID': generateUniqueId() // Optional but recommended for debugging
};

Integration Patterns

Pattern 1: Anonymous User Tracking

Start tracking users immediately when they land on your site, before any login or signup.

// When user first visits your site
async function initializeAnonymousTracking(campaignId = null, insiderId = null) {
  const fingerprintId = await generateFingerprint(); // Your fingerprinting logic
  
  try {
    const response = await fetch('/partner/v1/instance/anonymous', {
      method: 'POST',
      headers,
      body: JSON.stringify({
        fingerprint_id: fingerprintId,
        campaign_id: campaignId, // Optional: if user came from a specific campaign
        insider_id: insiderId    // Optional: if referred by another user
      })
    });
    
    const result = await response.json();
    
    if (result.success) {
      // Store instance ID in localStorage or session
      localStorage.setItem('cashin_instance_id', result.data.instance_id);
      localStorage.setItem('cashin_expires_at', result.data.expires_at);
      
      return result.data.instance_id;
    }
  } catch (error) {
    console.error('Failed to create anonymous instance:', error);
  }
}

Pattern 2: Instance Validation

Always validate instances before making critical operations or applying rewards.

async function validateCurrentInstance() {
  const instanceId = localStorage.getItem('cashin_instance_id');
  
  if (!instanceId) {
    return { valid: false, reason: 'No instance found' };
  }
  
  try {
    const response = await fetch('/partner/v1/instance/validate', {
      method: 'POST',
      headers,
      body: JSON.stringify({
        instance_id: instanceId
      })
    });
    
    const result = await response.json();
    
    if (result.success && result.data.valid) {
      return {
        valid: true,
        instance: result.data.instance,
        state: result.data.instance.instance_state
      };
    } else {
      // Instance expired or invalid - clean up local storage
      localStorage.removeItem('cashin_instance_id');
      localStorage.removeItem('cashin_expires_at');
      return { valid: false, reason: result.data.reason };
    }
  } catch (error) {
    console.error('Instance validation failed:', error);
    return { valid: false, reason: 'Validation error' };
  }
}

Pattern 3: Session Extension

Extend instances when users show continued engagement.

async function extendInstanceSession() {
  const instanceId = localStorage.getItem('cashin_instance_id');
  
  if (!instanceId) return false;
  
  try {
    const response = await fetch('/partner/v1/instance/extend', {
      method: 'POST',
      headers,
      body: JSON.stringify({
        instance_id: instanceId
      })
    });
    
    const result = await response.json();
    
    if (result.success) {
      // Update stored expiration time
      localStorage.setItem('cashin_expires_at', result.data.new_expires_at);
      console.log(`Instance extended by ${result.data.extended_by_hours} hours`);
      return true;
    }
  } catch (error) {
    console.error('Failed to extend instance:', error);
  }
  
  return false;
}

// Trigger extension on significant user actions
document.addEventListener('click', debounce(extendInstanceSession, 300000)); // Every 5 minutes max

Pattern 4: Clean Instance Termination

Properly revoke instances when users log out or leave.

async function terminateInstance() {
  const instanceId = localStorage.getItem('cashin_instance_id');
  
  if (!instanceId) return;
  
  try {
    const response = await fetch('/partner/v1/instance/revoke', {
      method: 'POST',
      headers,
      body: JSON.stringify({
        instance_id: instanceId
      })
    });
    
    const result = await response.json();
    
    if (result.success) {
      console.log('Instance revoked successfully');
    }
  } catch (error) {
    console.error('Failed to revoke instance:', error);
  } finally {
    // Always clean up local storage
    localStorage.removeItem('cashin_instance_id');
    localStorage.removeItem('cashin_expires_at');
  }
}

// Call on logout or page unload
window.addEventListener('beforeunload', terminateInstance);

Complete Integration Workflow

Step 1: Initialize on Page Load

async function initializeCashInTracking() {
  // Check for existing valid instance
  const validation = await validateCurrentInstance();
  
  if (validation.valid) {
    console.log('Existing instance found:', validation.instance.id);
    return validation.instance.id;
  }
  
  // Extract campaign info from URL if present
  const urlParams = new URLSearchParams(window.location.search);
  const campaignId = urlParams.get('campaign_id');
  const insiderId = urlParams.get('insider_id');
  
  // Create new anonymous instance
  return await initializeAnonymousTracking(campaignId, insiderId);
}

// Run on every page load
document.addEventListener('DOMContentLoaded', initializeCashInTracking);

Step 2: Handle User State Transitions

// When user signs up or logs in
async function handleUserAuthentication(userId) {
  const instanceId = localStorage.getItem('cashin_instance_id');
  
  if (instanceId) {
    // Validate current instance before proceeding
    const validation = await validateCurrentInstance();
    
    if (validation.valid) {
      // Instance will automatically upgrade to authenticated state
      // when CashIn detects the user login on your backend
      console.log('Instance will upgrade to authenticated state');
      
      // Optionally extend the session for authenticated users
      await extendInstanceSession();
    } else {
      // Create new instance for authenticated user
      await initializeAnonymousTracking();
    }
  }
}

Step 3: Implement Error Handling

class CashInInstanceManager {
  constructor(secretKey) {
    this.secretKey = secretKey;
    this.headers = {
      'Authorization': `Bearer ${secretKey}`,
      'Content-Type': 'application/json'
    };
  }
  
  async makeRequest(endpoint, data) {
    const requestId = this.generateRequestId();
    
    try {
      const response = await fetch(endpoint, {
        method: 'POST',
        headers: {
          ...this.headers,
          'X-Request-ID': requestId
        },
        body: JSON.stringify(data)
      });
      
      const result = await response.json();
      
      // Handle common errors
      if (!response.ok) {
        switch (response.status) {
          case 401:
            throw new Error('Invalid API key - check your authentication');
          case 429:
            throw new Error('Rate limit exceeded - please retry after delay');
          case 400:
            throw new Error(`Bad request: ${result.message}`);
          default:
            throw new Error(`API error: ${result.message}`);
        }
      }
      
      return result;
    } catch (error) {
      console.error(`Request ${requestId} failed:`, error);
      throw error;
    }
  }
  
  generateRequestId() {
    return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

Best Practices

Rate Limiting Compliance

// Implement exponential backoff for rate limits
async function withRateLimit(apiCall, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await apiCall();
    } catch (error) {
      if (error.message.includes('Rate limit') && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}

Instance Lifecycle Management

class InstanceLifecycleManager {
  constructor(apiManager) {
    this.api = apiManager;
    this.instanceId = null;
    this.expiresAt = null;
    this.checkInterval = null;
  }
  
  async initialize() {
    // Load from storage
    this.instanceId = localStorage.getItem('cashin_instance_id');
    this.expiresAt = localStorage.getItem('cashin_expires_at');
    
    // Validate existing instance
    if (this.instanceId && this.expiresAt) {
      const isValid = await this.validateInstance();
      if (!isValid) {
        await this.createNewInstance();
      }
    } else {
      await this.createNewInstance();
    }
    
    // Set up automatic validation checks
    this.startPeriodicValidation();
  }
  
  startPeriodicValidation() {
    // Check instance validity every 10 minutes
    this.checkInterval = setInterval(async () => {
      if (this.instanceId) {
        const isValid = await this.validateInstance();
        if (!isValid) {
          clearInterval(this.checkInterval);
          await this.createNewInstance();
          this.startPeriodicValidation();
        }
      }
    }, 600000); // 10 minutes
  }
  
  async validateInstance() {
    try {
      const result = await this.api.makeRequest('/partner/v1/instance/validate', {
        instance_id: this.instanceId
      });
      return result.success && result.data.valid;
    } catch (error) {
      return false;
    }
  }
  
  async createNewInstance() {
    try {
      const result = await this.api.makeRequest('/partner/v1/instance/anonymous', {
        fingerprint_id: await this.generateFingerprint(),
        campaign_id: this.extractCampaignId(),
        insider_id: this.extractInsiderId()
      });
      
      if (result.success) {
        this.instanceId = result.data.instance_id;
        this.expiresAt = result.data.expires_at;
        
        localStorage.setItem('cashin_instance_id', this.instanceId);
        localStorage.setItem('cashin_expires_at', this.expiresAt);
      }
    } catch (error) {
      console.error('Failed to create new instance:', error);
    }
  }
  
  cleanup() {
    if (this.checkInterval) {
      clearInterval(this.checkInterval);
    }
    
    if (this.instanceId) {
      // Attempt to revoke instance
      this.api.makeRequest('/partner/v1/instance/revoke', {
        instance_id: this.instanceId
      }).catch(error => {
        console.warn('Failed to revoke instance on cleanup:', error);
      });
    }
    
    localStorage.removeItem('cashin_instance_id');
    localStorage.removeItem('cashin_expires_at');
  }
}

Testing Your Integration

Unit Test Example

// Test instance creation and validation flow
async function testInstanceFlow() {
  const manager = new CashInInstanceManager('sk_test_...');
  
  console.log('Testing instance creation...');
  const createResult = await manager.makeRequest('/partner/v1/anonymous', {
    fingerprint_id: 'test-fingerprint-123',
    campaign_id: null,
    insider_id: null
  });
  
  console.log('Instance created:', createResult.data.instance_id);
  
  console.log('Testing instance validation...');
  const validateResult = await manager.makeRequest('/partner/v1/instance/validate', {
    instance_id: createResult.data.instance_id
  });
  
  console.log('Validation result:', validateResult.data.valid);
  
  console.log('Testing instance extension...');
  const extendResult = await manager.makeRequest('/partner/v1/instance/extend', {
    instance_id: createResult.data.instance_id
  });
  
  console.log('Extension result:', extendResult.data.new_expires_at);
  
  console.log('Testing instance revocation...');
  const revokeResult = await manager.makeRequest('/partner/v1/instance/revoke', {
    instance_id: createResult.data.instance_id
  });
  
  console.log('Revocation result:', revokeResult.success);
}

Debugging Tips

  1. Always include X-Request-ID headers for easier debugging with CashIn support

  2. Log instance state transitions to understand user journey flow

  3. Monitor instance expiration times to optimize extension timing

  4. Track validation failures to identify integration issues

  5. Use test API keys during development to avoid affecting production data

Common Integration Issues

Issue: Instance expires too quickly Solution: Implement proactive extension based on user activity patterns

Issue: Multiple instances created for same user Solution: Always validate existing instances before creating new ones

Issue: Campaign attribution not working Solution: Ensure campaign_id is properly extracted from URLs and passed during instance creation

Issue: Instance state not upgrading after login Solution: Verify your backend is properly communicating user authentication to CashIn

This integration guide provides the foundation for implementing Instance endpoints in your application. The instance system will automatically handle state transitions and campaign attribution once properly integrated.

Instance Endpoint Summary

Create Anonymous Instance

  • Use Case: Start tracking users before login/signup

  • Returns: New instance ID with 24-hour default expiration

  • Instance State: anonymous

Validate Instance

  • Use Case: Check session validity before operations

  • Returns: Instance details including state, user info, campaign data

  • States: anonymous, signup, authenticated

Extend Instance

  • Use Case: Keep active users' sessions alive

  • Returns: New expiration time (typically +24 hours)

  • Limitation: Cannot extend expired instances

Revoke Instance

  • Use Case: Clean logout, security revocation

  • Returns: Revocation timestamp

  • Effect: Instance becomes immediately invalid