Skip to main content

Overview

After receiving a session token from the handshake endpoint, you’ll embed Sticker into your application using an HTML iframe. This guide covers implementation, customization, and best practices.

Basic iframe Implementation

Use the iframe_embed_url returned from the handshake endpoint:
<iframe
  src="https://shop.usesticker.com/embedded/{partner_id}?session_key={token}"
  class="w-full h-full border-0"
  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"
></iframe>
Embedded Sticker Experience

Production iframe Code

Here’s the exact iframe configuration we use in production integrations:
<iframe
  src={iframeLink}
  className="w-full h-full border-0"
  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"
/>
Feel free to play around with these attributes for your platform. The sandbox and allow attributes above are the recommended minimum for full functionality.

iframe Attributes Explained

Required Attributes

src
string
required
The iframe embed URL returned from the handshake endpoint, or constructed manually:
https://shop.usesticker.com/embedded/{partner_id}?session_key={token}
sandbox
string
required
Security sandbox flags. Recommended configuration:
FlagPurpose
allow-same-originRequired for local storage and cookies
allow-scriptsRequired for app functionality
allow-formsRequired for search and checkout forms
allow-popupsRequired for payment processing windows
allow-popups-to-escape-sandboxRequired for Stripe payment popups
allow-top-navigation-by-user-activationAllows redirects after user interaction
allow
string
required
Feature policy permissions:
PermissionPurpose
paymentRequired for Stripe checkout
publickey-credentials-getEnables WebAuthn for secure payments
fullscreenAllows fullscreen mode if needed
title
string
Accessibility label for screen readers:
title="Sticker Embedded Procurement"
className or style
Style the iframe to fill your container:
class="w-full h-full border-0"
<!-- or -->
style="width: 100%; height: 100%; border: none;"

URL Structure

The iframe URL follows this structure:
https://shop.usesticker.com/embedded/{partner_id}?session_key={token}
PartDescription
shop.usesticker.comSticker’s embedded shop domain
/embedded/{partner_id}Partner-specific route with your UUID
?session_key={token}Session token from handshake
Use the iframe_embed_url from handshake: The handshake response includes a complete iframe_embed_url with your partner ID and session key already embedded. Use this directly instead of constructing the URL manually.

Complete Integration Examples

React / Next.js

import { useState, useEffect } from 'react';

function SuppliesModule({ userId }) {
  const [iframeUrl, setIframeUrl] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function authenticate() {
      try {
        setLoading(true);
        setError(null);
        
        // Call YOUR backend to get session token
        const response = await fetch('/api/supplies/auth', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ userId })
        });
        
        if (!response.ok) {
          throw new Error('Authentication failed');
        }
        
        const data = await response.json();
        setIframeUrl(data.iframe_url);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    
    authenticate();
  }, [userId]);

  if (loading) {
    return (
      <div className="flex items-center justify-center h-96">
        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-orange-500"></div>
        <span className="ml-2">Loading supplies...</span>
      </div>
    );
  }

  if (error) {
    return (
      <div className="flex flex-col items-center justify-center h-96">
        <p className="text-red-500 mb-4">Failed to load supplies: {error}</p>
        <button 
          onClick={() => window.location.reload()}
          className="px-4 py-2 bg-orange-500 text-white rounded hover:bg-orange-600"
        >
          Retry
        </button>
      </div>
    );
  }

  return (
    <iframe
      src={iframeUrl}
      className="w-full h-full border-0"
      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"
    />
  );
}

export default SuppliesModule;

Vue.js

<template>
  <div class="supplies-container">
    <!-- Loading State -->
    <div v-if="loading" class="loading-state">
      <div class="spinner"></div>
      <span>Loading supplies...</span>
    </div>
    
    <!-- Error State -->
    <div v-else-if="error" class="error-state">
      <p>Failed to load supplies: {{ error }}</p>
      <button @click="authenticate">Retry</button>
    </div>
    
    <!-- Embedded iframe -->
    <iframe
      v-else
      :src="iframeUrl"
      class="w-full h-full border-0"
      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>
</template>

<script>
export default {
  props: ['userId'],
  data() {
    return {
      iframeUrl: null,
      loading: true,
      error: null
    };
  },
  mounted() {
    this.authenticate();
  },
  methods: {
    async authenticate() {
      try {
        this.loading = true;
        this.error = null;
        
        const response = await fetch('/api/supplies/auth', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ userId: this.userId })
        });
        
        if (!response.ok) throw new Error('Authentication failed');
        
        const data = await response.json();
        this.iframeUrl = data.iframe_url;
      } catch (err) {
        this.error = err.message;
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

<style scoped>
.supplies-container {
  width: 100%;
  height: 100vh;
}

.loading-state, .error-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
}
</style>

Vanilla HTML/JavaScript

<!DOCTYPE html>
<html>
<head>
  <title>Supplies</title>
  <style>
    .supplies-container {
      width: 100%;
      height: 100vh;
      position: relative;
    }
    
    .supplies-container iframe {
      width: 100%;
      height: 100%;
      border: none;
    }
    
    .loading-overlay {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      background: #f5f5f5;
    }
    
    .hidden { display: none; }
  </style>
</head>
<body>
  <div id="supplies-container" class="supplies-container">
    <div id="loading" class="loading-overlay">
      Loading supplies...
    </div>
  </div>

  <script>
    async function loadSupplies(userId) {
      const container = document.getElementById('supplies-container');
      const loading = document.getElementById('loading');
      
      try {
        // Call your backend to get session token
        const response = await fetch('/api/supplies/auth', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ userId })
        });
        
        if (!response.ok) throw new Error('Authentication failed');
        
        const data = await response.json();
        
        // Create and append iframe
        const iframe = document.createElement('iframe');
        iframe.src = data.iframe_url;
        iframe.title = 'Sticker Embedded Procurement';
        iframe.sandbox = 'allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation';
        iframe.allow = 'payment; publickey-credentials-get; fullscreen';
        
        iframe.onload = () => {
          loading.classList.add('hidden');
        };
        
        container.appendChild(iframe);
        
      } catch (error) {
        loading.textContent = `Error: ${error.message}`;
      }
    }
    
    // Load supplies for current user
    loadSupplies('user-123');
  </script>
</body>
</html>

Responsive Design

Full-Height Container

Make the iframe fill the available space:
/* Parent container should have a defined height */
.supplies-page {
  height: calc(100vh - 64px); /* Subtract your header height */
}

.supplies-iframe {
  width: 100%;
  height: 100%;
  border: none;
}

Constrained Width with Centered Content

For a more focused experience on wide screens:
.supplies-wrapper {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  height: calc(100vh - 64px);
  padding: 0 16px;
}

.supplies-iframe {
  width: 100%;
  height: 100%;
  border: none;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

Mobile Optimization

.supplies-container {
  width: 100%;
  height: 100vh;
  overflow: auto;
  -webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
}

@media (max-width: 768px) {
  .supplies-container {
    padding: 0;
    /* Account for mobile browser chrome */
    height: calc(100vh - env(safe-area-inset-bottom));
  }
}

Security Considerations

The sandbox attribute restricts what the iframe can do. Our recommended configuration:
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
Do NOT add:
  • allow-top-navigation (allows iframe to navigate parent)
  • allow-modals (unnecessary)
Add Sticker domains to your CSP if you have one:
Content-Security-Policy: frame-src https://shop.usesticker.com https://*.usesticker.com;
Both your site and the iframe must use HTTPS. Mixed content (HTTP page with HTTPS iframe) will be blocked.

What the Embedded Experience Includes

The embedded experience includes:
  • Product Browsing - Search, filter, and browse products by category
  • Shopping Cart - Add items, adjust quantities, remove items
  • Multiple Shipping Locations - Select from org’s saved shipping addresses
  • Checkout - Apply coupons, select payment method, place orders
  • Order History - View past orders and their status
  • Profile Management - View account details and favorites

Troubleshooting

Possible causes:
  • Session token expired (>5 minutes old)
  • Session token already used
  • Invalid partner ID in URL
Solutions:
  • Generate a fresh session token
  • Verify the iframe_embed_url from handshake is correct
  • Check browser console for errors
Cause: Invalid session token or user not foundSolutions:
  • Ensure organization setup was completed first
  • Generate a new session token
  • Verify the internal_user_id matches what was used in setup
Cause: Missing iframe permissionsSolution: Ensure you have:
sandbox="... allow-popups allow-popups-to-escape-sandbox ..."
allow="payment; ..."
Cause: Content Security Policy blocking frameSolution: Add to your CSP:
frame-src https://shop.usesticker.com https://*.usesticker.com;
Cause: Container height not set properlySolution: Ensure parent container has explicit height:
.supplies-container {
  height: 100vh;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}

Best Practices

Generate Tokens On-Demand

Only generate session tokens when the user clicks to open supplies, not in advance

Show Loading States

Display a loading indicator while authenticating and loading the iframe

Handle Errors Gracefully

Show user-friendly error messages with retry options

Test on Mobile

Ensure the experience works well on phones and tablets

Use Full Height

Make the iframe fill available space for the best UX

Fresh Tokens for Each Session

Generate a new token every time the user navigates to supplies

Next Steps