Security

Security best practices for integrating Appizer

Implement Appizer securely to protect your data and users.

API Key Management

Never Expose Secret Keys

Secret keys (sk_) must never be exposed in client-side code:

// ❌ DANGER: Secret key in browser
const appizer = new Appizer({
  apiKey: 'sk_live_abc123...' // Exposed to users!
})

// ✅ SAFE: Use public key in browser
const appizer = new AppizerClient({
  publicKey: 'pk_live_xyz789...' // Safe for client-side
})

Use Environment Variables

Store API keys in environment variables:

# .env
APPIZER_API_KEY=sk_live_your_secret_key
APPIZER_PUBLIC_KEY=pk_live_your_public_key
// Server-side
const appizer = new Appizer({
  apiKey: process.env.APPIZER_API_KEY
})

// Client-side
const appizer = new AppizerClient({
  publicKey: process.env.NEXT_PUBLIC_APPIZER_PUBLIC_KEY
})

Critical

Add .env to your .gitignore to prevent committing secrets to version control.

Rotate Keys Regularly

Rotate API keys periodically:

Generate New Key

Create a new API key in your Appizer dashboard

Update Applications

Deploy the new key to all applications

Revoke Old Key

Delete the old key after confirming the new one works

Use Different Keys Per Environment

Separate keys for development, staging, and production:

# Development
APPIZER_API_KEY=sk_test_dev_abc123

# Staging
APPIZER_API_KEY=sk_test_staging_def456

# Production
APPIZER_API_KEY=sk_live_prod_ghi789

Data Privacy

PII Handling

Be careful with Personally Identifiable Information (PII):

// ❌ Avoid sending sensitive PII
appizer.identify({
  userId: 'user_123',
  traits: {
    ssn: '123-45-6789', // Don't send
    creditCard: '4111...', // Don't send
    password: 'secret123' // Never send
  }
})

// ✅ Hash or omit sensitive data
appizer.identify({
  userId: 'user_123',
  traits: {
    email: 'user@example.com', // OK if needed
    plan: 'enterprise', // Non-sensitive
    hashedId: sha256(userId) // Hashed if needed
  }
})

Data Minimization

Only track data you actually need:

// ❌ Over-collection
appizer.track({
  event: 'page_view',
  properties: {
    fullUrl: window.location.href,
    allCookies: document.cookie,
    localStorage: JSON.stringify(localStorage),
    userAgent: navigator.userAgent
  }
})

// ✅ Minimal collection
appizer.track({
  event: 'page_view',
  properties: {
    page: '/products',
    referrer: document.referrer
  }
})

Respect user privacy preferences:

// Check consent before tracking
if (userHasConsented()) {
  appizer.track({ event: 'page_view' })
}

// Provide opt-out mechanism
function optOutOfTracking() {
  appizer.optOut()
  localStorage.setItem('tracking_opted_out', 'true')
}

Network Security

Use HTTPS Only

Always use HTTPS for API requests:

const appizer = new Appizer({
  apiKey: process.env.APPIZER_API_KEY,
  baseUrl: 'https://api.appizer.com' // Always HTTPS
})

HTTP Blocked

Appizer blocks all HTTP requests. Only HTTPS is supported.

Validate SSL Certificates

Ensure SSL certificate validation is enabled:

const appizer = new Appizer({
  apiKey: process.env.APPIZER_API_KEY,
  validateSSL: true // Default: true
})

Set Request Timeouts

Prevent hanging connections:

const appizer = new Appizer({
  apiKey: process.env.APPIZER_API_KEY,
  timeout: 5000, // 5 seconds
  retries: 3
})

Input Validation

Sanitize User Input

Validate and sanitize data before tracking:

function sanitizeEventName(name: string): string {
  return name
    .toLowerCase()
    .replace(/[^a-z0-9_]/g, '_')
    .substring(0, 100)
}

function trackUserAction(action: string) {
  const sanitized = sanitizeEventName(action)
  appizer.track({ event: sanitized })
}

Validate Property Types

Ensure properties match expected types:

interface PurchaseProperties {
  amount: number
  currency: string
  productId: string
}

function trackPurchase(props: PurchaseProperties) {
  if (typeof props.amount !== 'number' || props.amount <= 0) {
    throw new Error('Invalid amount')
  }
  
  if (!/^[A-Z]{3}$/.test(props.currency)) {
    throw new Error('Invalid currency code')
  }
  
  appizer.track({
    event: 'purchase_completed',
    properties: props
  })
}

Limit Property Size

Prevent large payloads:

const MAX_PROPERTY_SIZE = 10000 // 10KB

function trackWithSizeLimit(event: string, properties: any) {
  const size = JSON.stringify(properties).length
  
  if (size > MAX_PROPERTY_SIZE) {
    console.error('Properties too large:', size)
    return
  }
  
  appizer.track({ event, properties })
}

Authentication

Server-Side Authentication

Perform sensitive operations server-side:

// ❌ Client-side (exposed)
appizer.identify({
  userId: 'user_123',
  traits: {
    internalScore: 95, // Sensitive
    accountBalance: 1000 // Sensitive
  }
})

// ✅ Server-side (protected)
// In your API endpoint
app.post('/api/identify-user', async (req, res) => {
  const { userId } = req.body
  
  await appizer.identify({
    userId,
    traits: {
      internalScore: calculateScore(userId),
      accountBalance: getBalance(userId)
    }
  })
  
  res.json({ success: true })
})

Verify Webhook Signatures

Validate webhook authenticity:

import crypto from 'crypto'

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex')
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  )
}

app.post('/webhooks/appizer', (req, res) => {
  const signature = req.headers['x-appizer-signature']
  const payload = JSON.stringify(req.body)
  
  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature')
  }
  
  // Process webhook
  res.status(200).send('OK')
})

Rate Limiting

Implement Client-Side Rate Limiting

Prevent excessive API calls:

class RateLimiter {
  private requests: number[] = []
  private limit = 100 // requests
  private window = 60000 // 1 minute
  
  canMakeRequest(): boolean {
    const now = Date.now()
    this.requests = this.requests.filter(t => t > now - this.window)
    
    if (this.requests.length >= this.limit) {
      return false
    }
    
    this.requests.push(now)
    return true
  }
}

const limiter = new RateLimiter()

function trackEvent(event: Event) {
  if (!limiter.canMakeRequest()) {
    console.warn('Rate limit exceeded')
    return
  }
  
  appizer.track(event)
}

Handle Rate Limit Errors

Gracefully handle 429 responses:

async function trackWithRetry(event: Event) {
  try {
    await appizer.track(event)
  } catch (error) {
    if (error.status === 429) {
      const retryAfter = error.headers['retry-after'] || 60
      console.log(`Rate limited. Retry after ${retryAfter}s`)
      
      setTimeout(() => trackWithRetry(event), retryAfter * 1000)
    }
  }
}

Error Handling

Don't Expose Internal Errors

Sanitize error messages:

try {
  await appizer.track(event)
} catch (error) {
  // ❌ Exposes internal details
  console.error('Track failed:', error)
  
  // ✅ Generic message
  console.error('Failed to track event')
  
  // Log detailed error server-side only
  if (process.env.NODE_ENV === 'development') {
    console.error('Details:', error)
  }
}

Implement Circuit Breaker

Prevent cascading failures:

class CircuitBreaker {
  private failures = 0
  private threshold = 5
  private timeout = 60000
  private state: 'closed' | 'open' | 'half-open' = 'closed'
  
  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      throw new Error('Circuit breaker is open')
    }
    
    try {
      const result = await fn()
      this.onSuccess()
      return result
    } catch (error) {
      this.onFailure()
      throw error
    }
  }
  
  private onSuccess() {
    this.failures = 0
    this.state = 'closed'
  }
  
  private onFailure() {
    this.failures++
    if (this.failures >= this.threshold) {
      this.state = 'open'
      setTimeout(() => {
        this.state = 'half-open'
      }, this.timeout)
    }
  }
}

Compliance

GDPR Compliance

Implement data subject rights:

// Right to access
async function exportUserData(userId: string) {
  return await appizer.users.export(userId)
}

// Right to deletion
async function deleteUserData(userId: string) {
  await appizer.users.delete(userId)
}

// Right to rectification
async function updateUserData(userId: string, traits: any) {
  await appizer.identify({ userId, traits })
}

Data Retention

Set appropriate retention policies:

// Configure retention in dashboard or API
await appizer.settings.update({
  dataRetention: {
    events: 90, // days
    users: 365 // days
  }
})

Security Checklist

API Keys

✓ Use environment variables
✓ Never commit to version control
✓ Separate keys per environment
✓ Rotate regularly

Data Privacy

✓ Minimize PII collection
✓ Hash sensitive data
✓ Obtain user consent
✓ Implement opt-out

Network

✓ Use HTTPS only
✓ Validate SSL certificates
✓ Set timeouts
✓ Implement rate limiting

Code

✓ Validate input
✓ Sanitize data
✓ Handle errors gracefully
✓ Use server-side for sensitive ops

Next Steps