Skip to main content

Overview

Security is paramount when integrating third-party services. This guide covers security best practices to protect your users and data.

API Key Security

Storage

DO:
# .env file (never commit!)
STICKER_API_KEY=sk_live_abc123...
STICKER_WEBHOOK_SECRET=whsec_xyz789...
DON’T:
// NEVER hardcode keys
const apiKey = 'sk_live_abc123...'; // ❌
Use dedicated secrets management for production:
  • AWS Secrets Manager
  • HashiCorp Vault
  • Azure Key Vault
  • Google Cloud Secret Manager
// Example with AWS Secrets Manager
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();

async function getAPIKey() {
  const secret = await secretsManager.getSecretValue({
    SecretId: 'sticker/api-key'
  }).promise();
  
  return JSON.parse(secret.SecretString).apiKey;
}
Rotate API keys regularly:
  1. Contact Sticker support to generate new key
  2. Update key in secrets manager
  3. Deploy configuration update
  4. Verify new key works
  5. Revoke old key
Recommended schedule: Every 90 days

Access Control

Limit who can access API keys:
// Use least privilege principle
const apiKey = process.env.STICKER_API_KEY;

// Only backend services should have access
if (typeof window !== 'undefined') {
  throw new Error('API key cannot be accessed in browser');
}
Never expose API keys in:
  • Client-side JavaScript
  • Mobile app code
  • Public repositories
  • Log files
  • Error messages
  • URLs or query parameters

Request Security

HTTPS Only

Always use HTTPS for API requests:
// Good
const API_URL = 'https://api.sticker.com';

// Bad - never use HTTP
const API_URL = 'http://api.sticker.com'; // ❌

Request Signing

Sign sensitive requests with HMAC:
const crypto = require('crypto');

function signRequest(body, secret) {
  // Use SHA-256 for cryptographic strength
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(body);
  
  return `sha256=${hmac.digest('hex')}`;
}

// Always use constant-time comparison
function verifySignature(received, expected) {
  const receivedBuffer = Buffer.from(received);
  const expectedBuffer = Buffer.from(expected);
  
  if (receivedBuffer.length !== expectedBuffer.length) {
    return false;
  }
  
  return crypto.timingSafeEqual(receivedBuffer, expectedBuffer);
}
Constant-time comparison prevents timing attacks that could leak information about the signature.

Input Validation

Validate all inputs before sending to API:
function validateOrganization(org) {
  const errors = [];
  
  // Email validation
  if (!org.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(org.email)) {
    errors.push('Invalid email format');
  }
  
  // Required fields
  if (!org.name || org.name.trim().length === 0) {
    errors.push('Organization name is required');
  }
  
  // Address validation
  if (!org.addresses || org.addresses.length === 0) {
    errors.push('At least one address is required');
  }
  
  org.addresses?.forEach((addr, idx) => {
    if (!addr.city || !addr.state || !addr.zip) {
      errors.push(`Address ${idx + 1} is incomplete`);
    }
    
    // Validate state code
    if (addr.state && addr.state.length !== 2) {
      errors.push(`Invalid state code in address ${idx + 1}`);
    }
  });
  
  return errors;
}

Rate Limiting

Implement client-side rate limiting:
class RateLimiter {
  constructor(maxRequests, windowMs) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = [];
  }

  async throttle() {
    const now = Date.now();
    
    // Remove old requests outside window
    this.requests = this.requests.filter(
      time => now - time < this.windowMs
    );
    
    // Check if at limit
    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = this.windowMs - (now - oldestRequest);
      await new Promise(resolve => setTimeout(resolve, waitTime));
      return this.throttle(); // Retry
    }
    
    // Add this request
    this.requests.push(now);
  }
}

// Usage
const limiter = new RateLimiter(100, 60000); // 100 per minute

async function callAPI(endpoint, data) {
  await limiter.throttle();
  return fetch(endpoint, data);
}

iframe Security

Sandbox Attribute

Use restrictive sandbox permissions:
<iframe
  src="https://app.sticker.com/embed?session_key=..."
  sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
  allow="payment"
/>
Safe permissions:
  • allow-same-origin - Required for local storage
  • allow-scripts - Required for functionality
  • allow-forms - Required for checkout
  • allow-popups - Required for payment windows
Avoid unless necessary:
  • allow-top-navigation - Can redirect parent page
  • allow-modals - Can show alerts in parent
  • allow-pointer-lock - Capture mouse
  • allow-same-origin allow-scripts together without sandboxing

postMessage Security

Always verify message origins:
window.addEventListener('message', (event) => {
  // CRITICAL: Verify origin
  if (event.origin !== 'https://app.sticker.com') {
    console.warn('Ignored message from unknown origin:', event.origin);
    return;
  }
  
  // Validate message structure
  if (!event.data || typeof event.data !== 'object') {
    console.warn('Invalid message format');
    return;
  }
  
  // Validate message type
  const allowedTypes = [
    'sticker:ready',
    'sticker:height',
    'sticker:cart_updated',
    'sticker:order_complete'
  ];
  
  if (!allowedTypes.includes(event.data.type)) {
    console.warn('Unknown message type:', event.data.type);
    return;
  }
  
  // Safe to process
  handleStickerMessage(event.data);
});

Content Security Policy

Configure CSP headers to allow Sticker iframe:
Content-Security-Policy:
  default-src 'self';
  frame-src https://app.sticker.com;
  connect-src 'self' https://api.sticker.com;
  img-src 'self' https://cdn.sticker.com;
  style-src 'self' 'unsafe-inline';

Data Security

PII Handling

Minimize PII sent to Sticker:
function sanitizeUserData(user) {
  // Only send necessary fields
  return {
    email: user.email,
    first_name: user.firstName,
    last_name: user.lastName
    // Don't send: SSN, DOB, medical records, etc.
  };
}

Data Encryption

Ensure data is encrypted in transit and at rest:
  • ✅ All API calls over HTTPS (TLS 1.2+)
  • ✅ Sticker encrypts data at rest
  • ✅ Session tokens are cryptographically random
  • ✅ Passwords never stored or transmitted

Data Retention

Understand data retention policies:
Data TypeRetention PeriodPurpose
Order historyIndefiniteReordering, support
Session tokens5 minutesAuthentication
Webhook logs30 daysDebugging
API logs90 daysSecurity audit

Webhook Security

Signature Verification

Always verify webhook signatures:
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  // Get expected signature
  const expectedSignature = `sha256=${crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex')}`;
  
  // Constant-time comparison
  try {
    const sigBuffer = Buffer.from(signature);
    const expectedBuffer = Buffer.from(expectedSignature);
    
    if (sigBuffer.length !== expectedBuffer.length) {
      return false;
    }
    
    return crypto.timingSafeEqual(sigBuffer, expectedBuffer);
  } catch (error) {
    return false;
  }
}

// Express middleware
app.post('/webhooks/sticker',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-sticker-signature'];
    const payload = req.body.toString('utf8');
    
    if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
      return res.status(401).send('Invalid signature');
    }
    
    // Process webhook
    const event = JSON.parse(payload);
    handleWebhook(event);
    res.status(200).send('OK');
  }
);

IP Allowlisting

Restrict webhook requests to Sticker’s IP ranges:
const STICKER_IP_RANGES = [
  '35.123.45.0/24',
  '52.234.56.0/24'
  // Contact Sticker for current IPs
];

function isAllowedIP(clientIP) {
  // Use a library like 'ip-range-check'
  return STICKER_IP_RANGES.some(range => 
    ipRangeCheck(clientIP, range)
  );
}

app.post('/webhooks/sticker', (req, res) => {
  const clientIP = req.ip;
  
  if (!isAllowedIP(clientIP)) {
    console.warn('Webhook from unauthorized IP:', clientIP);
    return res.status(403).send('Forbidden');
  }
  
  // Process webhook
});

Replay Protection

Prevent replay attacks:
const processedWebhooks = new Map();

function isReplayAttack(deliveryId, timestamp) {
  // Check if already processed
  if (processedWebhooks.has(deliveryId)) {
    return true;
  }
  
  // Check timestamp (reject if > 5 minutes old)
  const age = Date.now() - parseInt(timestamp) * 1000;
  if (age > 5 * 60 * 1000) {
    return true;
  }
  
  return false;
}

app.post('/webhooks/sticker', (req, res) => {
  const deliveryId = req.headers['x-sticker-delivery-id'];
  const timestamp = req.headers['x-sticker-timestamp'];
  
  if (isReplayAttack(deliveryId, timestamp)) {
    return res.status(400).send('Replay attack detected');
  }
  
  // Mark as processed
  processedWebhooks.set(deliveryId, Date.now());
  
  // Clean up old entries (after 1 hour)
  setTimeout(() => processedWebhooks.delete(deliveryId), 3600000);
  
  // Process webhook
});

Session Security

Token Generation

Session tokens must be cryptographically secure:
// Sticker generates tokens like this:
const crypto = require('crypto');

function generateSessionToken() {
  // 32 bytes = 64 hex characters
  return crypto.randomBytes(32).toString('hex');
}

Token Expiration

Enforce short token lifetimes:
  • ✅ Session tokens expire after 5 minutes
  • ✅ Single-use only
  • ✅ Cannot be refreshed
  • ✅ New token required for each session

Token Transmission

Safely pass tokens to iframe:
// Good - pass in URL
const iframeSrc = `https://app.sticker.com/embed?session_key=${token}`;

// Bad - never put in localStorage
localStorage.setItem('sticker_token', token); // ❌

Compliance

HIPAA Compliance

For healthcare data:
  • ✅ Sticker is HIPAA compliant
  • ✅ Business Associate Agreement (BAA) available
  • ✅ Data encrypted at rest and in transit
  • ✅ Audit logs maintained
  • ✅ Access controls enforced

SOC 2 Type II

Sticker maintains SOC 2 Type II certification:
  • Security controls
  • Availability guarantees
  • Processing integrity
  • Confidentiality measures
  • Privacy protection

GDPR

For EU users:
  • ✅ Data Processing Agreement (DPA) available
  • ✅ User data deletion on request
  • ✅ Data export functionality
  • ✅ Cookie consent handled
  • ✅ Privacy policy transparent

Security Monitoring

Log Security Events

Track security-relevant events:
function logSecurityEvent(event, severity, details) {
  const securityLog = {
    timestamp: new Date().toISOString(),
    event: event,
    severity: severity, // low, medium, high, critical
    details: details,
    ip: details.ip,
    user: details.user,
    action: details.action
  };
  
  // Send to security monitoring system
  securityLogger.log(securityLog);
  
  // Alert on critical events
  if (severity === 'critical') {
    alertSecurityTeam(securityLog);
  }
}

// Example usage
logSecurityEvent('api_key_exposure', 'critical', {
  message: 'API key found in client-side code',
  file: 'app.js',
  line: 123
});

Monitor for Anomalies

Detect suspicious activity:
class AnomalyDetector {
  constructor() {
    this.metrics = {
      requestRate: new Map(),
      errorRate: new Map(),
      ipActivity: new Map()
    };
  }

  trackRequest(orgId, ip, success) {
    // Track request rate per org
    const orgRequests = this.metrics.requestRate.get(orgId) || 0;
    this.metrics.requestRate.set(orgId, orgRequests + 1);
    
    // Alert on unusual rate
    if (orgRequests > 1000) { // per minute
      this.alert('High request rate', { orgId, rate: orgRequests });
    }
    
    // Track errors
    if (!success) {
      const orgErrors = this.metrics.errorRate.get(orgId) || 0;
      this.metrics.errorRate.set(orgId, orgErrors + 1);
      
      // Alert on high error rate
      if (orgErrors / orgRequests > 0.5) {
        this.alert('High error rate', { orgId, errorRate: orgErrors / orgRequests });
      }
    }
    
    // Track IP distribution
    const ipRequests = this.metrics.ipActivity.get(ip) || 0;
    this.metrics.ipActivity.set(ip, ipRequests + 1);
    
    // Alert on suspicious IP
    if (ipRequests > 500) {
      this.alert('Suspicious IP activity', { ip, requests: ipRequests });
    }
  }

  alert(message, details) {
    logSecurityEvent('anomaly_detected', 'high', {
      message,
      ...details
    });
  }
}

Incident Response

Response Plan

Have a plan for security incidents:
  1. Detect - Monitor for suspicious activity
  2. Contain - Immediately revoke compromised keys
  3. Assess - Determine scope of breach
  4. Notify - Inform affected parties
  5. Recover - Restore normal operations
  6. Learn - Update security measures

Emergency Contacts

Keep these contacts handy:

Key Compromise

If API key is compromised:
# 1. Immediately notify Sticker
Email: security@sticker.com
Subject: URGENT - API Key Compromise

# 2. Revoke the key
# Contact support or use partner dashboard

# 3. Generate new key
# Sticker support will issue a new key

# 4. Update all systems
# Deploy new key to all services

# 5. Audit access
# Review logs for unauthorized use

Security Checklist

1

API Keys

  • Keys stored in environment variables/secrets manager
  • No keys in source code or version control
  • Key rotation schedule established
  • Different keys for sandbox and production
  • Access limited to authorized personnel
2

Requests

  • All requests over HTTPS
  • Request signing implemented for handshake
  • Input validation before API calls
  • Rate limiting implemented
  • Error messages don’t leak sensitive data
3

iframe

  • Sandbox attribute configured
  • postMessage origin verification
  • CSP headers configured
  • HTTPS required for parent page
  • No sensitive data in iframe URL
4

Webhooks

  • Signature verification implemented
  • IP allowlisting configured
  • Replay protection in place
  • Webhook endpoint uses HTTPS
  • Secrets rotated regularly
5

Monitoring

  • Security events logged
  • Anomaly detection active
  • Alerts configured
  • Regular security audits scheduled
  • Incident response plan documented

Resources