Skip to content

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 โ€‹

The usage of this API is at your own risk. Attivita GmbH is not responsible for any damages or losses.