Security Best Practices
Essential security practices for implementing the Smartlinks API safely and securely
Security Best Practices
Follow these security guidelines to keep your Smartlinks integration secure.
API Key Management
Never Expose API Keys
❌ Bad - Exposing in Client Code:
// NEVER DO THIS
const apiKey = 'sk_live_abc123...'; // Hardcoded API key
fetch('https://api.smartlinks.io/v1/links', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
✅ Good - Server-side Only:
// Server-side code (Node.js)
const apiKey = process.env.SMARTLINKS_API_KEY;
app.post('/api/create-link', async (req, res) => {
const response = await fetch('https://api.smartlinks.io/v1/links', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(req.body)
});
const data = await response.json();
res.json(data);
});
Environment Variables
Store API keys securely:
# .env file (add to .gitignore!)
SMARTLINKS_API_KEY=sk_live_your_key_here
SMARTLINKS_WEBHOOK_SECRET=whsec_your_secret_here
// Load environment variables
require('dotenv').config();
const apiKey = process.env.SMARTLINKS_API_KEY;
const webhookSecret = process.env.SMARTLINKS_WEBHOOK_SECRET;
Key Rotation
Rotate API keys regularly:
// Support multiple API keys during rotation
const primaryKey = process.env.SMARTLINKS_API_KEY_PRIMARY;
const secondaryKey = process.env.SMARTLINKS_API_KEY_SECONDARY;
async function makeRequestWithFallback(endpoint, options) {
try {
// Try primary key first
return await makeRequest(endpoint, primaryKey, options);
} catch (error) {
if (error.status === 401) {
// Fallback to secondary key
return await makeRequest(endpoint, secondaryKey, options);
}
throw error;
}
}
Rate Limiting
Implement client-side rate limiting:
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}
async acquire() {
const now = Date.now();
this.requests = this.requests.filter(
time => now - time < this.windowMs
);
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.acquire();
}
this.requests.push(now);
}
}
// Usage: 100 requests per minute
const limiter = new RateLimiter(100, 60000);
async function createLink(url) {
await limiter.acquire();
return fetch('https://api.smartlinks.io/v1/links', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ destination_url: url })
});
}
Input Validation
Always validate and sanitize user input:
const validator = require('validator');
function validateLinkInput(input) {
const errors = [];
// Validate destination URL
if (!input.destination_url) {
errors.push('Destination URL is required');
} else if (!validator.isURL(input.destination_url, {
protocols: ['http', 'https'],
require_protocol: true
})) {
errors.push('Invalid destination URL');
}
// Validate custom slug
if (input.custom_slug) {
if (!/^[a-zA-Z0-9-_]+$/.test(input.custom_slug)) {
errors.push('Custom slug can only contain letters, numbers, hyphens, and underscores');
}
if (input.custom_slug.length > 50) {
errors.push('Custom slug must be 50 characters or less');
}
}
return errors;
}
// Usage
app.post('/api/create-link', (req, res) => {
const errors = validateLinkInput(req.body);
if (errors.length > 0) {
return res.status(400).json({ errors });
}
// Proceed with link creation
createLink(req.body)
.then(link => res.json(link))
.catch(error => res.status(500).json({ error: error.message }));
});
Webhook Security
Verify Signatures
Always verify webhook signatures:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(JSON.stringify(payload)).digest('hex');
// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}
app.post('/webhooks/smartlinks', express.json(), (req, res) => {
const signature = req.headers['x-smartlinks-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(req.body, signature, secret)) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Unauthorized' });
}
handleWebhook(req.body);
res.status(200).json({ received: true });
});
Idempotency
Handle duplicate webhook events:
const processedEvents = new Set();
async function handleWebhook(event) {
// Check if already processed
if (processedEvents.has(event.id)) {
console.log('Duplicate event, skipping:', event.id);
return;
}
// Process the event
await processEvent(event);
// Mark as processed
processedEvents.add(event.id);
// Clean up old events (older than 24 hours)
cleanupOldEvents();
}
HTTPS Only
Always use HTTPS for API requests:
const BASE_URL = 'https://api.smartlinks.io/v1'; // Always HTTPS
// Reject non-HTTPS URLs
function ensureHttps(url) {
const parsed = new URL(url);
if (parsed.protocol !== 'https:') {
throw new Error('Only HTTPS URLs are allowed');
}
return url;
}
Error Handling
Don't expose sensitive information in errors:
// ❌ Bad - Exposes sensitive info
app.post('/api/create-link', async (req, res) => {
try {
const link = await createLink(req.body);
res.json(link);
} catch (error) {
res.status(500).json({ error: error.message }); // May expose API key or internal details
}
});
// ✅ Good - Generic error message
app.post('/api/create-link', async (req, res) => {
try {
const link = await createLink(req.body);
res.json(link);
} catch (error) {
console.error('Link creation error:', error); // Log internally
res.status(500).json({
error: 'Failed to create link. Please try again.'
});
}
});
Monitoring & Logging
Log security-relevant events:
function logSecurityEvent(event, details) {
const log = {
timestamp: new Date().toISOString(),
event: event,
details: details,
ip: details.ip,
userAgent: details.userAgent
};
// Send to logging service
logger.security(log);
// Alert on suspicious activity
if (event === 'invalid_signature' || event === 'rate_limit_exceeded') {
alertSecurityTeam(log);
}
}
Security Checklist
- ✅ Store API keys in environment variables
- ✅ Never commit secrets to version control
- ✅ Use HTTPS for all API requests
- ✅ Verify webhook signatures
- ✅ Implement rate limiting
- ✅ Validate all user input
- ✅ Use idempotency keys for webhooks
- ✅ Rotate API keys regularly
- ✅ Log security events
- ✅ Handle errors securely
- ❌ Never expose API keys in client-side code
- ❌ Never log sensitive data