Skip to main content

Overview

Follow these best practices to ensure your Sticker integration is secure, performant, and provides the best user experience.

Authentication & Security

DO:
  • Store API keys in environment variables
  • Use secrets management systems (AWS Secrets Manager, HashiCorp Vault)
  • Rotate keys regularly
  • Use different keys for sandbox and production
DON’T:
  • Hardcode API keys in source code
  • Commit keys to version control
  • Share keys in chat/email
  • Expose keys in client-side code
DO:
  • Sign the exact request body string (no modifications)
  • Use constant-time comparison for signatures
  • Generate fresh signatures for each request
  • Log signature mismatches for debugging
DON’T:
  • Modify request body after signing
  • Cache signatures
  • Use simple string comparison (timing attacks)
  • Skip signature validation in development
DO:
  • Generate tokens on-demand when user opens module
  • Handle token expiration gracefully
  • Regenerate tokens on authentication errors
  • Clear tokens after user logs out
DON’T:
  • Pre-generate tokens in advance
  • Cache or store session tokens
  • Reuse tokens across sessions
  • Share tokens between users
DO:
  • Use sandbox attribute with minimal permissions
  • Verify postMessage origin strictly
  • Implement Content Security Policy
  • Use HTTPS for all environments
DON’T:
  • Allow allow-top-navigation unnecessarily
  • Trust messages from unknown origins
  • Mix HTTP and HTTPS content
  • Disable security features for convenience

Error Handling

Implement Retry Logic

Use exponential backoff for transient failures:
async function callStickerAPI(endpoint, options, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(endpoint, options);
      
      // Don't retry client errors (4xx)
      if (response.status >= 400 && response.status < 500) {
        throw new Error(`Client error: ${response.status}`);
      }
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return await response.json();
    } catch (error) {
      lastError = error;
      
      // Don't retry on last attempt
      if (attempt === maxRetries - 1) {
        break;
      }
      
      // Exponential backoff: 1s, 2s, 4s
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

Handle Rate Limiting

Respect rate limits and implement backoff:
async function handleRateLimit(response) {
  if (response.status === 429) {
    const retryAfter = response.headers.get('Retry-After') || 60;
    
    console.warn(`Rate limited. Retrying after ${retryAfter} seconds`);
    
    await new Promise(resolve => 
      setTimeout(resolve, parseInt(retryAfter) * 1000)
    );
    
    // Retry the request
    return fetch(response.url, response.options);
  }
  
  return response;
}

User-Friendly Error Messages

Transform technical errors into helpful messages:
function getUserFriendlyError(error) {
  const errorMap = {
    'ORGANIZATION_NOT_FOUND': 'Your organization needs to be set up. Please contact support.',
    'USER_NOT_FOUND': 'Your account is not authorized. Please contact your administrator.',
    'INVALID_SIGNATURE': 'Authentication error. Please try again.',
    'SESSION_EXPIRED': 'Your session has expired. Refreshing...',
    'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment and try again.',
  };
  
  return errorMap[error.code] || 'An error occurred. Please try again or contact support.';
}

Performance Optimization

Cache Organization Data

Cache organization setup data to avoid redundant API calls:
// Simple in-memory cache
const orgCache = new Map();

async function getOrCreateOrganization(orgData) {
  const cacheKey = orgData.id;
  
  // Check cache first
  if (orgCache.has(cacheKey)) {
    const cached = orgCache.get(cacheKey);
    
    // Cache valid for 24 hours
    if (Date.now() - cached.timestamp < 24 * 60 * 60 * 1000) {
      return cached.data;
    }
  }
  
  // Call API
  const result = await setupOrganization(orgData);
  
  // Cache the result
  orgCache.set(cacheKey, {
    data: result,
    timestamp: Date.now()
  });
  
  return result;
}

Preload iframe

Start loading the iframe while generating the session token:
async function loadSuppliesModule(orgId, user) {
  // Start both operations in parallel
  const [sessionData, _] = await Promise.all([
    handshakeUser(orgId, user),
    preloadIframe() // Start loading iframe assets
  ]);
  
  // iframe is now ready to receive session token
  displayIframe(sessionData.session_key);
}

function preloadIframe() {
  return new Promise(resolve => {
    const link = document.createElement('link');
    link.rel = 'preconnect';
    link.href = 'https://app.sticker.com';
    document.head.appendChild(link);
    setTimeout(resolve, 100);
  });
}

Monitor Performance

Track integration performance metrics:
class StickerMetrics {
  static trackHandshake(startTime, success) {
    const duration = Date.now() - startTime;
    
    // Send to your analytics
    analytics.track('sticker_handshake', {
      duration_ms: duration,
      success: success
    });
  }
  
  static trackIframeLoad(startTime) {
    const duration = Date.now() - startTime;
    
    analytics.track('sticker_iframe_load', {
      duration_ms: duration
    });
  }
  
  static trackError(endpoint, error) {
    analytics.track('sticker_error', {
      endpoint: endpoint,
      error_code: error.code,
      error_message: error.message
    });
  }
}

// Usage
const start = Date.now();
try {
  const result = await handshakeUser(orgId, user);
  StickerMetrics.trackHandshake(start, true);
} catch (error) {
  StickerMetrics.trackHandshake(start, false);
  StickerMetrics.trackError('handshake', error);
}

User Experience

Loading States

Always show loading indicators:
function SuppliesModule() {
  const [state, setState] = useState('initializing'); // initializing, loading, ready, error
  
  return (
    <div className="supplies-wrapper">
      {state === 'initializing' && (
        <div className="loading-state">
          <Spinner />
          <p>Preparing supplies module...</p>
        </div>
      )}
      
      {state === 'loading' && (
        <div className="loading-state">
          <Spinner />
          <p>Loading products...</p>
        </div>
      )}
      
      {state === 'error' && (
        <div className="error-state">
          <ErrorIcon />
          <p>Failed to load supplies</p>
          <button onClick={retry}>Try Again</button>
        </div>
      )}
      
      <iframe
        src={iframeSrc}
        style={{ display: state === 'ready' ? 'block' : 'none' }}
        onLoad={() => setState('ready')}
      />
    </div>
  );
}

Graceful Degradation

Provide fallback options when integration fails:
function SuppliesModuleWithFallback() {
  const [integrationFailed, setIntegrationFailed] = useState(false);
  
  if (integrationFailed) {
    return (
      <div className="fallback-message">
        <h3>Supplies Module Unavailable</h3>
        <p>We're experiencing technical difficulties. Please:</p>
        <ul>
          <li>Try again in a few minutes</li>
          <li>Contact support: support@sticker.com</li>
          <li>Call us: 1-800-STICKER</li>
        </ul>
      </div>
    );
  }
  
  return <StickerEmbed onError={() => setIntegrationFailed(true)} />;
}

Communicate Order Status

Keep users informed about their orders:
function handleOrderComplete(orderId) {
  // Show success message
  showNotification({
    type: 'success',
    title: 'Order Placed Successfully!',
    message: `Your order #${orderId} has been placed and will be processed soon.`
  });
  
  // Update your system
  syncOrder(orderId);
  
  // Send confirmation email
  sendOrderConfirmation(orderId);
  
  // Navigate to orders page
  setTimeout(() => {
    router.push(`/orders/${orderId}`);
  }, 2000);
}

Data Management

Sync Organization Data

Keep organization data in sync:
// Sync on a regular schedule
async function syncOrganizationData(orgId) {
  try {
    // Get latest data from your system
    const currentData = await getOrganizationData(orgId);
    
    // Update Sticker if changes detected
    if (hasChanges(currentData)) {
      await updateStickerOrganization(currentData);
    }
  } catch (error) {
    console.error('Org sync failed:', error);
    // Alert admins about sync failure
    alertAdmins('Organization sync failed', error);
  }
}

// Run daily
setInterval(() => syncOrganizationData(orgId), 24 * 60 * 60 * 1000);

Handle User Updates

Update user profiles when names change:
async function handleUserProfileUpdate(user) {
  // The handshake endpoint automatically updates names
  // Just make sure to pass the latest user data
  const sessionData = await handshakeUser(orgId, {
    email: user.email,
    first_name: user.firstName, // Latest name
    last_name: user.lastName
  });
  
  return sessionData;
}

Monitoring & Observability

Log All API Interactions

Maintain comprehensive logs:
class StickerLogger {
  static logRequest(endpoint, requestData) {
    logger.info('Sticker API Request', {
      endpoint,
      org_id: requestData.partner_org_id,
      timestamp: new Date().toISOString()
    });
  }
  
  static logResponse(endpoint, status, responseTime) {
    logger.info('Sticker API Response', {
      endpoint,
      status,
      response_time_ms: responseTime,
      timestamp: new Date().toISOString()
    });
  }
  
  static logError(endpoint, error) {
    logger.error('Sticker API Error', {
      endpoint,
      error_code: error.code,
      error_message: error.message,
      stack_trace: error.stack,
      timestamp: new Date().toISOString()
    });
  }
}

Set Up Alerts

Monitor critical metrics:
// Alert on high error rate
if (errorRate > 0.05) { // 5% error rate
  alertOps({
    severity: 'high',
    message: 'Sticker integration error rate above threshold',
    metric: 'error_rate',
    value: errorRate
  });
}

// Alert on slow responses
if (avgResponseTime > 2000) { // 2 seconds
  alertOps({
    severity: 'medium',
    message: 'Sticker API response time degraded',
    metric: 'response_time',
    value: avgResponseTime
  });
}

Testing

Integration Tests

Test your integration thoroughly:
describe('Sticker Integration', () => {
  it('should setup organization successfully', async () => {
    const result = await setupOrganization(mockOrgData);
    
    expect(result.success).toBe(true);
    expect(result.profile.id).toBeDefined();
  });
  
  it('should handle handshake and return session token', async () => {
    const result = await handshakeUser(orgId, mockUser);
    
    expect(result.session_key).toBeDefined();
    expect(result.session_key).toHaveLength(64);
  });
  
  it('should retry on transient failures', async () => {
    // Mock API to fail twice, then succeed
    mockAPI.failureCount = 2;
    
    const result = await callStickerAPI(endpoint, options);
    
    expect(result).toBeDefined();
    expect(mockAPI.callCount).toBe(3);
  });
  
  it('should handle rate limiting', async () => {
    mockAPI.rateLimitNextRequest();
    
    const result = await callStickerAPI(endpoint, options);
    
    expect(result).toBeDefined();
    expect(mockAPI.retryCount).toBe(1);
  });
});

Use Sandbox Environment

Always test in sandbox first:
const config = {
  sandbox: {
    apiUrl: 'https://sandbox.api.sticker.com',
    apiKey: process.env.STICKER_SANDBOX_API_KEY
  },
  production: {
    apiUrl: 'https://api.sticker.com',
    apiKey: process.env.STICKER_API_KEY
  }
};

const env = process.env.NODE_ENV === 'production' ? 'production' : 'sandbox';
const stickerConfig = config[env];

Deployment Checklist

Before going live, verify:
1

Security

  • API keys stored securely in environment variables
  • Request signing implemented correctly
  • iframe sandbox attributes configured
  • CSP headers include Sticker domains
  • HTTPS enabled on all pages
2

Error Handling

  • Retry logic with exponential backoff
  • Rate limiting handled gracefully
  • User-friendly error messages
  • Fallback UI for failures
  • Error logging and monitoring
3

User Experience

  • Loading states for all async operations
  • Responsive design on mobile devices
  • Accessible (screen reader compatible)
  • Order completion notifications
  • Clear navigation and CTAs
4

Performance

  • iframe loads in < 3 seconds
  • API calls complete in < 2 seconds
  • No unnecessary re-renders
  • Metrics tracking implemented
  • Performance monitoring active
5

Testing

  • All integration tests passing
  • Tested in sandbox environment
  • Manual QA completed
  • Cross-browser testing done
  • Mobile testing completed

Support & Maintenance

Stay Updated

  • Subscribe to the Sticker changelog
  • Join the partner community forum
  • Attend quarterly partner webinars
  • Review API deprecation notices

Have a Rollback Plan

// Feature flag for Sticker integration
const STICKER_ENABLED = process.env.FEATURE_STICKER === 'true';

function SuppliesRoute() {
  if (STICKER_ENABLED) {
    return <StickerEmbed />;
  } else {
    return <LegacySuppliesModule />;
  }
}

Monitor Integration Health

Create a health check dashboard:
async function checkStickerHealth() {
  const checks = {
    api_reachable: false,
    authentication_working: false,
    iframe_loading: false,
    avg_response_time: 0
  };
  
  try {
    const start = Date.now();
    const response = await fetch('https://api.sticker.com/health');
    checks.api_reachable = response.ok;
    checks.avg_response_time = Date.now() - start;
  } catch (error) {
    console.error('Health check failed:', error);
  }
  
  return checks;
}

Getting Help