> ## 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.

# Best Practices

> Tips and patterns for a robust Sticker integration

## Overview

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

## Authentication & Security

<AccordionGroup>
  <Accordion title="Protect Your API Key" icon="key">
    **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
  </Accordion>

  <Accordion title="Session Token Management" icon="ticket">
    **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
  </Accordion>

  <Accordion title="iframe Security" icon="shield">
    **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
  </Accordion>
</AccordionGroup>

## Error Handling

### Implement Retry Logic

Use exponential backoff for transient failures:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
// ❌ 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:

```html theme={null}
<!-- 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:

```javascript theme={null}
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:

```jsx theme={null}
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:

```jsx theme={null}
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:

```javascript theme={null}
// 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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

<Steps>
  <Step title="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
  </Step>

  <Step title="Error Handling">
    * [ ] Retry logic with exponential backoff
    * [ ] Rate limiting handled gracefully
    * [ ] User-friendly error messages
    * [ ] Fallback UI for failures
  </Step>

  <Step title="User Experience">
    * [ ] Loading states for all async operations
    * [ ] Responsive design on mobile devices
    * [ ] Clear error messaging
    * [ ] Easy retry/refresh options
  </Step>

  <Step title="Performance">
    * [ ] iframe loads in \< 3 seconds
    * [ ] API calls complete in \< 2 seconds
    * [ ] Metrics tracking implemented
  </Step>

  <Step title="Testing">
    * [ ] All integration tests passing
    * [ ] Tested in sandbox environment
    * [ ] Manual QA completed
    * [ ] Cross-browser testing done
    * [ ] Mobile testing completed
  </Step>
</Steps>

## Getting Help

<CardGroup cols={2}>
  <Card title="Partner Support" icon="headset" href="mailto:suyash@usesticker.com">
    Technical support for integration issues
  </Card>

  <Card title="Schedule Call" icon="calendar" href="https://calendly.com/usesticker/meeting">
    Book time with solutions engineers
  </Card>
</CardGroup>
