Overview
Security is paramount when integrating third-party services. This guide covers security best practices to protect your users and data.
API Key Security
Storage
DO: # .env file (never commit!)
STICKER_API_KEY = sk_live_abc123...
STICKER_WEBHOOK_SECRET = whsec_xyz789...
DON’T: // NEVER hardcode keys
const apiKey = 'sk_live_abc123...' ; // ❌
Use dedicated secrets management for production:
AWS Secrets Manager
HashiCorp Vault
Azure Key Vault
Google Cloud Secret Manager
// Example with AWS Secrets Manager
const AWS = require ( 'aws-sdk' );
const secretsManager = new AWS . SecretsManager ();
async function getAPIKey () {
const secret = await secretsManager . getSecretValue ({
SecretId: 'sticker/api-key'
}). promise ();
return JSON . parse ( secret . SecretString ). apiKey ;
}
Rotate API keys regularly:
Contact Sticker support to generate new key
Update key in secrets manager
Deploy configuration update
Verify new key works
Revoke old key
Recommended schedule: Every 90 days
Access Control
Limit who can access API keys:
// Use least privilege principle
const apiKey = process . env . STICKER_API_KEY ;
// Only backend services should have access
if ( typeof window !== 'undefined' ) {
throw new Error ( 'API key cannot be accessed in browser' );
}
Never expose API keys in:
Client-side JavaScript
Mobile app code
Public repositories
Log files
Error messages
URLs or query parameters
Request Security
HTTPS Only
Always use HTTPS for API requests:
// Good
const API_URL = 'https://api.sticker.com' ;
// Bad - never use HTTP
const API_URL = 'http://api.sticker.com' ; // ❌
Request Signing
Sign sensitive requests with HMAC:
const crypto = require ( 'crypto' );
function signRequest ( body , secret ) {
// Use SHA-256 for cryptographic strength
const hmac = crypto . createHmac ( 'sha256' , secret );
hmac . update ( body );
return `sha256= ${ hmac . digest ( 'hex' ) } ` ;
}
// Always use constant-time comparison
function verifySignature ( received , expected ) {
const receivedBuffer = Buffer . from ( received );
const expectedBuffer = Buffer . from ( expected );
if ( receivedBuffer . length !== expectedBuffer . length ) {
return false ;
}
return crypto . timingSafeEqual ( receivedBuffer , expectedBuffer );
}
Constant-time comparison prevents timing attacks that could leak information about the signature.
Validate all inputs before sending to API:
function validateOrganization ( org ) {
const errors = [];
// Email validation
if ( ! org . email || ! / ^ [ ^ \s@ ] + @ [ ^ \s@ ] + \. [ ^ \s@ ] + $ / . test ( org . email )) {
errors . push ( 'Invalid email format' );
}
// Required fields
if ( ! org . name || org . name . trim (). length === 0 ) {
errors . push ( 'Organization name is required' );
}
// Address validation
if ( ! org . addresses || org . addresses . length === 0 ) {
errors . push ( 'At least one address is required' );
}
org . addresses ?. forEach (( addr , idx ) => {
if ( ! addr . city || ! addr . state || ! addr . zip ) {
errors . push ( `Address ${ idx + 1 } is incomplete` );
}
// Validate state code
if ( addr . state && addr . state . length !== 2 ) {
errors . push ( `Invalid state code in address ${ idx + 1 } ` );
}
});
return errors ;
}
Rate Limiting
Implement client-side rate limiting:
class RateLimiter {
constructor ( maxRequests , windowMs ) {
this . maxRequests = maxRequests ;
this . windowMs = windowMs ;
this . requests = [];
}
async throttle () {
const now = Date . now ();
// Remove old requests outside window
this . requests = this . requests . filter (
time => now - time < this . windowMs
);
// Check if at limit
if ( this . requests . length >= this . maxRequests ) {
const oldestRequest = this . requests [ 0 ];
const waitTime = this . windowMs - ( now - oldestRequest );
await new Promise ( resolve => setTimeout ( resolve , waitTime ));
return this . throttle (); // Retry
}
// Add this request
this . requests . push ( now );
}
}
// Usage
const limiter = new RateLimiter ( 100 , 60000 ); // 100 per minute
async function callAPI ( endpoint , data ) {
await limiter . throttle ();
return fetch ( endpoint , data );
}
iframe Security
Sandbox Attribute
Use restrictive sandbox permissions:
< iframe
src = "https://app.sticker.com/embed?session_key=..."
sandbox = "allow-same-origin allow-scripts allow-forms allow-popups"
allow = "payment"
/ >
Safe permissions:
allow-same-origin - Required for local storage
allow-scripts - Required for functionality
allow-forms - Required for checkout
allow-popups - Required for payment windows
Avoid unless necessary:
allow-top-navigation - Can redirect parent page
allow-modals - Can show alerts in parent
allow-pointer-lock - Capture mouse
allow-same-origin allow-scripts together without sandboxing
postMessage Security
Always verify message origins:
window . addEventListener ( 'message' , ( event ) => {
// CRITICAL: Verify origin
if ( event . origin !== 'https://app.sticker.com' ) {
console . warn ( 'Ignored message from unknown origin:' , event . origin );
return ;
}
// Validate message structure
if ( ! event . data || typeof event . data !== 'object' ) {
console . warn ( 'Invalid message format' );
return ;
}
// Validate message type
const allowedTypes = [
'sticker:ready' ,
'sticker:height' ,
'sticker:cart_updated' ,
'sticker:order_complete'
];
if ( ! allowedTypes . includes ( event . data . type )) {
console . warn ( 'Unknown message type:' , event . data . type );
return ;
}
// Safe to process
handleStickerMessage ( event . data );
});
Content Security Policy
Configure CSP headers to allow Sticker iframe:
Content-Security-Policy:
default-src 'self';
frame-src https://app.sticker.com;
connect-src 'self' https://api.sticker.com;
img-src 'self' https://cdn.sticker.com;
style-src 'self' 'unsafe-inline';
Data Security
PII Handling
Minimize PII sent to Sticker:
function sanitizeUserData ( user ) {
// Only send necessary fields
return {
email: user . email ,
first_name: user . firstName ,
last_name: user . lastName
// Don't send: SSN, DOB, medical records, etc.
};
}
Data Encryption
Ensure data is encrypted in transit and at rest:
✅ All API calls over HTTPS (TLS 1.2+)
✅ Sticker encrypts data at rest
✅ Session tokens are cryptographically random
✅ Passwords never stored or transmitted
Data Retention
Understand data retention policies:
Data Type Retention Period Purpose Order history Indefinite Reordering, support Session tokens 5 minutes Authentication Webhook logs 30 days Debugging API logs 90 days Security audit
Webhook Security
Signature Verification
Always verify webhook signatures:
const crypto = require ( 'crypto' );
function verifyWebhookSignature ( payload , signature , secret ) {
// Get expected signature
const expectedSignature = `sha256= ${ crypto
. createHmac ( 'sha256' , secret )
. update ( payload )
. digest ( 'hex' ) } ` ;
// Constant-time comparison
try {
const sigBuffer = Buffer . from ( signature );
const expectedBuffer = Buffer . from ( expectedSignature );
if ( sigBuffer . length !== expectedBuffer . length ) {
return false ;
}
return crypto . timingSafeEqual ( sigBuffer , expectedBuffer );
} catch ( error ) {
return false ;
}
}
// Express middleware
app . post ( '/webhooks/sticker' ,
express . raw ({ type: 'application/json' }),
( req , res ) => {
const signature = req . headers [ 'x-sticker-signature' ];
const payload = req . body . toString ( 'utf8' );
if ( ! verifyWebhookSignature ( payload , signature , WEBHOOK_SECRET )) {
return res . status ( 401 ). send ( 'Invalid signature' );
}
// Process webhook
const event = JSON . parse ( payload );
handleWebhook ( event );
res . status ( 200 ). send ( 'OK' );
}
);
IP Allowlisting
Restrict webhook requests to Sticker’s IP ranges:
const STICKER_IP_RANGES = [
'35.123.45.0/24' ,
'52.234.56.0/24'
// Contact Sticker for current IPs
];
function isAllowedIP ( clientIP ) {
// Use a library like 'ip-range-check'
return STICKER_IP_RANGES . some ( range =>
ipRangeCheck ( clientIP , range )
);
}
app . post ( '/webhooks/sticker' , ( req , res ) => {
const clientIP = req . ip ;
if ( ! isAllowedIP ( clientIP )) {
console . warn ( 'Webhook from unauthorized IP:' , clientIP );
return res . status ( 403 ). send ( 'Forbidden' );
}
// Process webhook
});
Replay Protection
Prevent replay attacks:
const processedWebhooks = new Map ();
function isReplayAttack ( deliveryId , timestamp ) {
// Check if already processed
if ( processedWebhooks . has ( deliveryId )) {
return true ;
}
// Check timestamp (reject if > 5 minutes old)
const age = Date . now () - parseInt ( timestamp ) * 1000 ;
if ( age > 5 * 60 * 1000 ) {
return true ;
}
return false ;
}
app . post ( '/webhooks/sticker' , ( req , res ) => {
const deliveryId = req . headers [ 'x-sticker-delivery-id' ];
const timestamp = req . headers [ 'x-sticker-timestamp' ];
if ( isReplayAttack ( deliveryId , timestamp )) {
return res . status ( 400 ). send ( 'Replay attack detected' );
}
// Mark as processed
processedWebhooks . set ( deliveryId , Date . now ());
// Clean up old entries (after 1 hour)
setTimeout (() => processedWebhooks . delete ( deliveryId ), 3600000 );
// Process webhook
});
Session Security
Token Generation
Session tokens must be cryptographically secure:
// Sticker generates tokens like this:
const crypto = require ( 'crypto' );
function generateSessionToken () {
// 32 bytes = 64 hex characters
return crypto . randomBytes ( 32 ). toString ( 'hex' );
}
Token Expiration
Enforce short token lifetimes:
✅ Session tokens expire after 5 minutes
✅ Single-use only
✅ Cannot be refreshed
✅ New token required for each session
Token Transmission
Safely pass tokens to iframe:
// Good - pass in URL
const iframeSrc = `https://app.sticker.com/embed?session_key= ${ token } ` ;
// Bad - never put in localStorage
localStorage . setItem ( 'sticker_token' , token ); // ❌
Compliance
HIPAA Compliance
For healthcare data:
✅ Sticker is HIPAA compliant
✅ Business Associate Agreement (BAA) available
✅ Data encrypted at rest and in transit
✅ Audit logs maintained
✅ Access controls enforced
SOC 2 Type II
Sticker maintains SOC 2 Type II certification:
Security controls
Availability guarantees
Processing integrity
Confidentiality measures
Privacy protection
GDPR
For EU users:
✅ Data Processing Agreement (DPA) available
✅ User data deletion on request
✅ Data export functionality
✅ Cookie consent handled
✅ Privacy policy transparent
Security Monitoring
Log Security Events
Track security-relevant events:
function logSecurityEvent ( event , severity , details ) {
const securityLog = {
timestamp: new Date (). toISOString (),
event: event ,
severity: severity , // low, medium, high, critical
details: details ,
ip: details . ip ,
user: details . user ,
action: details . action
};
// Send to security monitoring system
securityLogger . log ( securityLog );
// Alert on critical events
if ( severity === 'critical' ) {
alertSecurityTeam ( securityLog );
}
}
// Example usage
logSecurityEvent ( 'api_key_exposure' , 'critical' , {
message: 'API key found in client-side code' ,
file: 'app.js' ,
line: 123
});
Monitor for Anomalies
Detect suspicious activity:
class AnomalyDetector {
constructor () {
this . metrics = {
requestRate: new Map (),
errorRate: new Map (),
ipActivity: new Map ()
};
}
trackRequest ( orgId , ip , success ) {
// Track request rate per org
const orgRequests = this . metrics . requestRate . get ( orgId ) || 0 ;
this . metrics . requestRate . set ( orgId , orgRequests + 1 );
// Alert on unusual rate
if ( orgRequests > 1000 ) { // per minute
this . alert ( 'High request rate' , { orgId , rate: orgRequests });
}
// Track errors
if ( ! success ) {
const orgErrors = this . metrics . errorRate . get ( orgId ) || 0 ;
this . metrics . errorRate . set ( orgId , orgErrors + 1 );
// Alert on high error rate
if ( orgErrors / orgRequests > 0.5 ) {
this . alert ( 'High error rate' , { orgId , errorRate: orgErrors / orgRequests });
}
}
// Track IP distribution
const ipRequests = this . metrics . ipActivity . get ( ip ) || 0 ;
this . metrics . ipActivity . set ( ip , ipRequests + 1 );
// Alert on suspicious IP
if ( ipRequests > 500 ) {
this . alert ( 'Suspicious IP activity' , { ip , requests: ipRequests });
}
}
alert ( message , details ) {
logSecurityEvent ( 'anomaly_detected' , 'high' , {
message ,
... details
});
}
}
Incident Response
Response Plan
Have a plan for security incidents:
Detect - Monitor for suspicious activity
Contain - Immediately revoke compromised keys
Assess - Determine scope of breach
Notify - Inform affected parties
Recover - Restore normal operations
Learn - Update security measures
Keep these contacts handy:
Key Compromise
If API key is compromised:
# 1. Immediately notify Sticker
Email: security@sticker.com
Subject: URGENT - API Key Compromise
# 2. Revoke the key
# Contact support or use partner dashboard
# 3. Generate new key
# Sticker support will issue a new key
# 4. Update all systems
# Deploy new key to all services
# 5. Audit access
# Review logs for unauthorized use
Security Checklist
Resources