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 >
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
The iframe embed URL returned from the handshake endpoint, or constructed manually: https://shop.usesticker.com/embedded/{partner_id}?session_key={token}
Security sandbox flags. Recommended configuration: Flag Purpose 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
Feature policy permissions: Permission Purpose paymentRequired for Stripe checkout publickey-credentials-getEnables WebAuthn for secure payments fullscreenAllows fullscreen mode if needed
Recommended Attributes
Accessibility label for screen readers: title="Sticker Embedded Procurement"
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}
Part Description 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 : 100 vh ;
}
.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 : 100 vh ;
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 ( 100 vh - 64 px ); /* 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 : 1400 px ;
margin : 0 auto ;
height : calc ( 100 vh - 64 px );
padding : 0 16 px ;
}
.supplies-iframe {
width : 100 % ;
height : 100 % ;
border : none ;
border-radius : 8 px ;
box-shadow : 0 2 px 10 px rgba ( 0 , 0 , 0 , 0.1 );
}
Mobile Optimization
.supplies-container {
width : 100 % ;
height : 100 vh ;
overflow : auto ;
-webkit-overflow-scrolling : touch ; /* Smooth scrolling on iOS */
}
@media ( max-width : 768 px ) {
.supplies-container {
padding : 0 ;
/* Account for mobile browser chrome */
height : calc ( 100 vh - 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
iframe Shows Blank/White Screen
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
iframe Shows Authentication Error
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; ..."
iframe Blocked by Browser
Cause: Content Security Policy blocking frameSolution: Add to your CSP:frame-src https://shop.usesticker.com https://*.usesticker.com;
Scrolling Issues on Mobile
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