Skip to main content

Documentation Index

Fetch the complete documentation index at: https://embed.usesticker.com/llms.txt

Use this file to discover all available pages before exploring further.

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)
  • Use different keys for sandbox and production
  • Keep keys server-side only
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:
  • Generate tokens on-demand when user opens module
  • Handle token expiration gracefully (tokens last 5 min)
  • Regenerate tokens on authentication errors
  • Clear any references when 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 proper permissions
  • Implement Content Security Policy
  • Use HTTPS for all environments
DON’T:
  • Remove sandbox permissions unnecessarily
  • 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 except 429)
      if (response.status >= 400 && response.status < 500 && response.status !== 429) {
        const error = await response.json();
        throw new Error(`Client error: ${error.message}`);
      }
      
      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, retryFn) {
  if (response.status === 429) {
    const data = await response.json();
    const retryAfter = data.retry_after || 60;
    
    console.warn(`Rate limited. Retrying after ${retryAfter} seconds`);
    
    await new Promise(resolve => 
      setTimeout(resolve, retryAfter * 1000)
    );
    
    // Retry the request
    return retryFn();
  }
  
  return response;
}

User-Friendly Error Messages

Transform technical errors into helpful messages:
function getUserFriendlyError(error) {
  const errorMap = {
    'VALIDATION_ERROR': 'Please check your information and try again.',
    'UNAUTHORIZED': 'Authentication failed. Please refresh and try again.',
    'USER_ALREADY_EXISTS': 'This user is already set up.',
    'Profile not found': 'Your account needs to be set up. Please contact your administrator.',
    'SESSION_EXPIRED': 'Your session has expired. Refreshing...',
    'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment and try again.',
  };
  
  return errorMap[error.code] || errorMap[error.message] || 
    'An error occurred. Please try again or contact support.';
}

Performance Optimization

Don’t Cache Session Tokens

Session tokens are single-use and short-lived—never cache them:
// ❌ BAD - Never cache session tokens
const tokenCache = new Map();
function getCachedToken(userId) {
  return tokenCache.get(userId);
}

// ✅ GOOD - Generate fresh each time
async function getSessionToken(userId) {
  const response = await fetch('/api/supplies/auth', {
    method: 'POST',
    body: JSON.stringify({ userId })
  });
  return response.json();
}

Preconnect to Sticker Domain

Speed up iframe loading:
<!-- Add to your HTML head -->
<link rel="preconnect" href="https://shop.usesticker.com" />
<link rel="dns-prefetch" href="https://shop.usesticker.com" />

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 getSessionToken(userId);
  StickerMetrics.trackHandshake(start, true);
} catch (error) {
  StickerMetrics.trackHandshake(start, false);
  StickerMetrics.trackError('handshake', error);
}

User Experience

Loading States

Always show loading indicators:
function SuppliesModule({ userId }) {
  const [state, setState] = useState('initializing');
  const [iframeUrl, setIframeUrl] = useState(null);

  useEffect(() => {
    async function load() {
      try {
        setState('loading');
        const { iframe_url } = await getSessionToken(userId);
        setIframeUrl(iframe_url);
        setState('ready');
      } catch (error) {
        setState('error');
      }
    }
    load();
  }, [userId]);

  return (
    <div className="supplies-wrapper">
      {state === 'loading' && (
        <div className="loading-state">
          <Spinner />
          <p>Loading supplies...</p>
        </div>
      )}
      
      {state === 'error' && (
        <div className="error-state">
          <ErrorIcon />
          <p>Failed to load supplies</p>
          <button onClick={() => window.location.reload()}>Try Again</button>
        </div>
      )}
      
      {iframeUrl && (
        <iframe
          src={iframeUrl}
          className="w-full h-full border-0"
          style={{ display: state === 'ready' ? 'block' : 'none' }}
          onLoad={() => setState('ready')}
          title="Sticker Embedded Procurement"
          sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
          allow="payment; publickey-credentials-get; fullscreen"
        />
      )}
    </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: suyash@usesticker.com</li>
        </ul>
        <button onClick={() => setIntegrationFailed(false)}>
          Try Again
        </button>
      </div>
    );
  }
  
  return <StickerEmbed onError={() => setIntegrationFailed(true)} />;
}

Data Management

Use Your Internal IDs

Always use your internal identifiers for easy correlation:
// Good - uses your internal IDs
await setupOrganization({
  internalOrgId: organization.id,           // Your org ID
  internalUserId: user.id,                  // Your user ID
  shippingLocations: locations.map(loc => ({
    internalShippingLocationId: loc.id,     // Your location ID
    ...
  }))
});

// Later, for handshake
await handshake({
  internal_user_id: user.id                 // Same ID you used in setup
});

Handle Existing Users Gracefully

The setup endpoint returns 409 if user already exists—don’t treat this as an error:
async function ensureUserSetup(org, user) {
  try {
    const result = await setupOrganization(org, user);
    
    if (result.data.isNewOrganization) {
      console.log('New organization created');
    } else {
      console.log('User added to existing organization');
    }
    
    return result.data.profile.id;
  } catch (error) {
    // 409 means user already exists - that's fine!
    if (error.code === 'USER_ALREADY_EXISTS') {
      console.log('User already set up:', error.data.profileId);
      return error.data.profileId;
    }
    throw error;
  }
}

Testing

Integration Tests

Test your integration thoroughly:
describe('Sticker Integration', () => {
  it('should setup organization successfully', async () => {
    const result = await setupOrganization(mockOrgData, mockUser, mockLocations);
    
    expect(result.success).toBe(true);
    expect(result.data.profile.id).toBeDefined();
    expect(result.data.organization.id).toBeDefined();
  });
  
  it('should handle handshake and return session token', async () => {
    const result = await getHandshakeToken('test-user-123');
    
    expect(result.session_key).toBeDefined();
    expect(result.session_key).toHaveLength(64);
    expect(result.iframe_embed_url).toContain('session_key');
  });
  
  it('should retry on transient failures', async () => {
    mockAPI.failNextRequests(2);  // Fail twice, succeed third
    
    const result = await callStickerAPI(endpoint, options);
    
    expect(result).toBeDefined();
    expect(mockAPI.callCount).toBe(3);
  });
  
  it('should return user-friendly error for missing profile', async () => {
    mockAPI.returnError(404, { error: 'Profile not found' });
    
    const error = await callStickerAPI(endpoint, options).catch(e => e);
    const friendlyMessage = getUserFriendlyError(error);
    
    expect(friendlyMessage).not.toContain('404');
    expect(friendlyMessage).toContain('account');
  });
});

Use Sandbox Environment

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

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

Deployment Checklist

Before going live, verify:
1

Security

  • API keys stored securely in environment variables
  • API key never exposed in client-side code
  • iframe sandbox attributes configured
  • 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
3

User Experience

  • Loading states for all async operations
  • Responsive design on mobile devices
  • Clear error messaging
  • Easy retry/refresh options
4

Performance

  • iframe loads in < 3 seconds
  • API calls complete in < 2 seconds
  • Metrics tracking implemented
5

Testing

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

Getting Help

Partner Support

Technical support for integration issues

Schedule Call

Book time with solutions engineers