Best Practices โ
Follow these guidelines to build robust and efficient integrations with the Attivita API.
Authentication & Security โ
๐ Protect Your API Token โ
Never expose tokens
- โ Don't commit tokens to version control
- โ Don't include tokens in client-side code
- โ Don't share tokens in logs or error messages
Secure storage
javascript
// Use environment variables
const API_TOKEN = process.env.ATTIVITA_API_TOKEN;
// Or secure key management services
const API_TOKEN = await secretManager.getSecret('attivita-api-token');๐ก๏ธ Always Use HTTPS โ
All API requests must use HTTPS:
javascript
// โ
Correct
const url = 'https://api.attivita.de/api/products';
// โ Never use HTTP
const url = 'http://api.attivita.de/api/products';Error Handling โ
๐จ Implement Comprehensive Error Handling โ
javascript
async function safeApiCall(fn) {
try {
return await fn();
} catch (error) {
// Log error details for debugging
console.error({
timestamp: new Date().toISOString(),
error: error.message,
stack: error.stack,
context: fn.name
});
// Handle specific error types
if (error.response) {
switch (error.response.status) {
case 401:
// Handle authentication errors
await refreshToken();
break;
case 429:
// Handle rate limiting
await waitForRateLimit(error.response);
break;
case 500:
// Retry server errors
return retryWithBackoff(fn);
}
}
throw error;
}
}๐ Implement Retry Logic โ
javascript
async function retryWithBackoff(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const waitTime = delay * Math.pow(2, i);
console.log(`Retry ${i + 1}/${maxRetries} after ${waitTime}ms`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
}Performance Optimization โ
๐ฆ Cache When Appropriate โ
javascript
class CachedApiClient {
constructor(apiToken, cacheTTL = 300000) { // 5 minutes
this.apiToken = apiToken;
this.cache = new Map();
this.cacheTTL = cacheTTL;
}
async getProduct(productId) {
const cacheKey = `product:${productId}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
return cached.data;
}
const product = await this.fetchProduct(productId);
this.cache.set(cacheKey, {
data: product,
timestamp: Date.now()
});
return product;
}
}๐ Batch Operations โ
Instead of multiple individual requests:
javascript
// โ Inefficient
const orders = [];
for (const orderNumber of orderNumbers) {
const order = await api.getOrder(orderNumber);
orders.push(order);
}
// โ
Better - use bulk endpoints when available
const allOrders = await api.getOrders();
const orders = allOrders.filter(o => orderNumbers.includes(o.orderNumber));Testing Strategy โ
๐งช Always Test in Sandbox First โ
javascript
class AttivitaClient {
constructor(apiToken, options = {}) {
this.apiToken = apiToken;
this.sandboxMode = options.sandboxMode || false;
}
async createOrder(products) {
const headers = {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json'
};
if (this.sandboxMode) {
headers['X-Sandbox-Mode'] = 'true';
}
const response = await fetch('/api/orders', {
method: 'POST',
headers,
body: JSON.stringify({ products })
});
return response.json();
}
}
// Development
const devClient = new AttivitaClient(token, { sandboxMode: true });
// Production
const prodClient = new AttivitaClient(token, { sandboxMode: false });โ Validate Before Sending โ
javascript
function validateOrderRequest(products) {
const errors = [];
if (!Array.isArray(products) || products.length === 0) {
errors.push('Products array is required');
}
products.forEach((product, index) => {
if (!product.productId) {
errors.push(`products[${index}].productId is required`);
}
if (!product.amount || product.amount < 1) {
errors.push(`products[${index}].amount must be positive`);
}
});
if (errors.length > 0) {
throw new ValidationError(errors);
}
}Webhook Best Practices โ
๐ Always Verify Signatures โ
javascript
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(computed)
);
}โก Respond Quickly โ
javascript
app.post('/webhook', async (req, res) => {
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously
setImmediate(async () => {
try {
await processWebhookEvent(req.body);
} catch (error) {
console.error('Webhook processing failed:', error);
}
});
});๐ Handle Duplicates โ
javascript
const processedEvents = new Set();
async function processWebhookEvent(event) {
const eventId = `${event.event}-${event.timestamp}-${event.data.productId}`;
if (processedEvents.has(eventId)) {
console.log('Duplicate event, skipping:', eventId);
return;
}
processedEvents.add(eventId);
// Clean up old events (keep last 1000)
if (processedEvents.size > 1000) {
const firstEvent = processedEvents.values().next().value;
processedEvents.delete(firstEvent);
}
// Process the event
await handleEvent(event);
}Monitoring & Logging โ
๐ Track API Usage โ
javascript
class ApiMetrics {
constructor() {
this.requests = [];
this.errors = [];
}
logRequest(endpoint, duration, status) {
this.requests.push({
endpoint,
duration,
status,
timestamp: Date.now()
});
// Keep last hour of data
const oneHourAgo = Date.now() - 3600000;
this.requests = this.requests.filter(r => r.timestamp > oneHourAgo);
}
getStats() {
const total = this.requests.length;
const avgDuration = this.requests.reduce((sum, r) => sum + r.duration, 0) / total;
const errorRate = this.requests.filter(r => r.status >= 400).length / total;
return {
totalRequests: total,
averageDuration: avgDuration,
errorRate: errorRate,
requestsPerMinute: total / 60
};
}
}๐ Structured Logging โ
javascript
const logger = {
info: (message, context = {}) => {
console.log(JSON.stringify({
level: 'info',
message,
timestamp: new Date().toISOString(),
...context
}));
},
error: (message, error, context = {}) => {
console.error(JSON.stringify({
level: 'error',
message,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
...context
}));
}
};
// Usage
logger.info('Order created', {
orderNumber: order.orderNumber,
customerId: order.customer,
total: order.grandTotal
});Production Checklist โ
Before going to production, ensure:
- [ ] โ All sandbox tests pass
- [ ] ๐ API tokens are securely stored
- [ ] ๐จ Error handling is comprehensive
- [ ] ๐ Monitoring is in place
- [ ] ๐ Retry logic is implemented
- [ ] ๐๏ธ Caching strategy is defined
- [ ] ๐ Webhook signatures are verified
- [ ] ๐ Logging captures key events
- [ ] โก Rate limits are respected
- [ ] ๐งช Integration tests are written
Common Pitfalls to Avoid โ
โ Don't Poll Excessively โ
javascript
// Bad - Polling every second
setInterval(checkProductStock, 1000);
// Good - Use webhooks or reasonable intervals
setInterval(checkProductStock, 300000); // 5 minutesโ Don't Ignore Rate Limits โ
javascript
// Bad - No rate limit handling
while (hasMore) {
const data = await fetchNextPage();
// ...
}
// Good - Respect rate limits
while (hasMore) {
const response = await fetchNextPage();
if (response.headers['x-ratelimit-remaining'] < 10) {
await sleep(60000); // Wait a minute
}
// ...
}โ Don't Store Sensitive Data โ
javascript
// Bad - Logging sensitive data
console.log('Created order:', JSON.stringify(order));
// Good - Sanitize logs
console.log('Created order:', {
orderNumber: order.orderNumber,
itemCount: order.items.length,
total: order.grandTotal
// Don't log: licenses, customer details
});Next Steps โ
- Review Error Handling documentation
- Set up Webhooks for real-time updates
- Configure Rate Limiting strategies
- Implement Webhook Security