Best Practices

Follow these recommendations for secure, reliable, and performant integrations with Vobiz. Learn from our experience and avoid common pitfalls.

Security Best Practices

Never Hardcode Credentials

❌ Bad Practice

Hardcoded Credentials (Never Do This)
const API_KEY = "sk_live_abc123def456";  // NEVER DO THIS
const PASSWORD = "my_secret_password";    // NEVER DO THIS

✅ Good Practice

Use Environment Variables
// Load from environment variables
const API_KEY = process.env.VOBIZ_API_KEY;
const PASSWORD = process.env.VOBIZ_PASSWORD;

// Or use a secrets manager
const secrets = await secretsManager.getSecret("vobiz/credentials");

Use HTTPS for All Requests

Always use https:// URLs when calling the API. Never send authentication tokens or sensitive data over unencrypted HTTP connections. Our API only responds to HTTPS.

Rotate Credentials Regularly

  • Change trunk passwords every 90 days
  • Change account passwords regularly and invalidate old refresh tokens
  • Immediately rotate if credentials are suspected to be compromised
  • Use sub-accounts for different applications/teams to limit blast radius

Whitelist IP Addresses

For production SIP trunks with static IPs, use IP authentication instead of passwords:

IP Whitelist Configuration
{
  "authType": "ip",
  "ipWhitelist": [
    "203.0.113.10",  // Production PBX
    "203.0.113.11"   // Backup PBX
  ]
}

This eliminates password-based attacks and simplifies configuration for trusted systems.

Validate Input on Client Side

Implement client-side validation before sending requests to prevent injection attacks:

  • Sanitize phone numbers (remove special characters, validate E.164 format)
  • Validate email addresses match RFC 5322 spec
  • Limit string lengths (trunk names, usernames, etc.)
  • Reject suspicious patterns (SQL injection attempts, XSS payloads)

Authentication & Authorization

Token Storage

⚠️ Frontend Applications

  • Store access tokens in memory or sessionStorage (expires when tab closes)
  • Never store tokens in localStorage (vulnerable to XSS attacks)
  • Use httpOnly cookies for refresh tokens if possible

✅ Backend Applications

  • Store tokens in environment variables or secrets manager
  • Encrypt tokens at rest if storing in database
  • Use secure memory storage for short-lived tokens

Automatic Token Refresh

Implement automatic token refresh before expiration (30 minutes for access tokens):

Token Refresh Pattern
async function makeAuthenticatedRequest(url, options) {
  // Check if token expires in next 5 minutes
  if (tokenExpiresAt - Date.now() < 5 * 60 * 1000) {
    await refreshAccessToken();
  }

  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${accessToken}`
    }
  });
}

async function refreshAccessToken() {
  const response = await fetch('https://api.vobiz.ai/api/v1/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refreshToken })
  });

  const data = await response.json();
  accessToken = data.access_token;
  tokenExpiresAt = Date.now() + (30 * 60 * 1000);
}

Use Sub-Accounts for Separation

Create separate sub-accounts for different applications, environments, or teams:

  • Development: Separate sub-account for testing
  • Production: Dedicated sub-account with stricter controls
  • Partners: Sub-accounts for each integration partner
  • Departments: Sales, Support, Engineering teams get own sub-accounts

Performance Optimization

Implement Caching

Cache infrequently changing data to reduce API calls:

ResourceCache TTLNotes
Account info15 minutesRarely changes
Trunk list5 minutesInvalidate on create/delete
Balance1 minuteFrequently updated
CDRsNo cacheAlways fetch fresh

Use Pagination Wisely

Don't fetch all records at once. Use pagination for large datasets:

Efficient Pagination
// Fetch CDRs in batches of 100
async function getAllCDRs(accountId, startDate, endDate) {
  const allCDRs = [];
  let offset = 0;
  const limit = 100;

  while (true) {
    const response = await fetch(
      `/api/v1/account/${accountId}/cdr?start_date=${startDate}&end_date=${endDate}&limit=${limit}&offset=${offset}`
    );
    const data = await response.json();

    allCDRs.push(...data.data.cdrs);

    if (!data.data.pagination.hasMore) break;
    offset += limit;
  }

  return allCDRs;
}

Batch Operations

Instead of making multiple individual requests, consider implementing client-side batching:

  • Batch CDR queries by date ranges instead of querying each day separately
  • Combine multiple resource updates in a single transaction when possible
  • Use bulk operations for importing/exporting large datasets

Connection Pooling

Reuse HTTP connections instead of creating new ones for every request:

Connection Pooling (Node.js)
const https = require('https');

// Create agent with connection pooling
const agent = new https.Agent({
  keepAlive: true,
  maxSockets: 50,
  maxFreeSockets: 10,
  timeout: 60000
});

// Reuse agent for all requests
fetch(url, { agent });

Reliability & Error Handling

Implement Retry Logic

Retry failed requests with exponential backoff for transient errors:

Retry with Exponential Backoff
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      // Don't retry 4xx errors (client errors)
      if (response.status >= 400 && response.status < 500) {
        return response;
      }

      // Retry 5xx errors and network failures
      if (response.ok) {
        return response;
      }

      if (attempt === maxRetries) {
        throw new Error(`Failed after ${maxRetries} retries`);
      }

      // Exponential backoff: 1s, 2s, 4s, 8s...
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));

    } catch (error) {
      if (attempt === maxRetries) throw error;

      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Circuit Breaker Pattern

Prevent cascading failures by implementing a circuit breaker:

  • Track consecutive failures for each service
  • After 5 failures, "open" circuit and fail fast for 30 seconds
  • After timeout, allow one request through ("half-open" state)
  • If successful, close circuit; if failed, reopen for longer

Handle Rate Limits Gracefully

When you receive a 429 (Rate Limit) response:

Handle Rate Limits
async function handleRateLimit(response) {
  if (response.status === 429) {
    const retryAfter = response.headers.get('Retry-After') || 60;

    console.log(`Rate limited. Retrying after ${retryAfter} seconds`);

    await new Promise(resolve =>
      setTimeout(resolve, retryAfter * 1000)
    );

    // Retry the request
    return fetch(url, options);
  }

  return response;
}

Idempotency Keys

For critical operations (purchases, transactions), use idempotency keys to prevent duplicates:

Idempotent Requests
const idempotencyKey = `purchase-${Date.now()}-${Math.random()}`;

await fetch('/api/v1/account/ACC123/numbers/purchase', {
  method: 'POST',
  headers: {
    'Idempotency-Key': idempotencyKey,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ phoneNumber: '+14155551234' })
});

Monitoring & Logging

Log All API Requests

Maintain detailed logs for debugging and auditing:

Request Logging
function logAPIRequest(method, url, status, duration, requestId) {
  console.log({
    timestamp: new Date().toISOString(),
    method,
    url,
    status,
    duration: `${duration}ms`,
    requestId,
    service: 'vobiz-api'
  });
}

Track Key Metrics

  • • API request success/failure rates
  • • Average response times per endpoint
  • • Error distribution by error code
  • • Token refresh frequency
  • • Balance consumption rate
  • • Call success rates (ASR)
  • • Rate limit violations

Set Up Alerts

Configure alerts for critical events:

  • Balance Low: Alert when balance drops below threshold
  • Error Spike: Alert when error rate exceeds 5% of requests
  • Trunk Down: Alert when trunk authentication fails repeatedly
  • Rate Limit Hit: Alert when approaching rate limits

Testing Strategies

Use Separate Accounts for Testing

Create dedicated test accounts/sub-accounts with separate credentials. Never test in production with real customer data or active trunks.

Test Error Scenarios

Don't just test happy paths. Verify your application handles errors correctly: invalid credentials, insufficient balance, rate limits, network timeouts, etc.

Automate Integration Tests

Use the Postman collection with Newman CLI in your CI/CD pipeline to catch integration regressions early. Run tests on every commit to main branch.

Monitor Test Call Quality

Make regular test calls and verify: • Call connects within 3 seconds (low PDD)
• Audio quality is clear (no packet loss)
• CDR is generated correctly
• Balance is debited accurately

Load Testing

Before going live, test with realistic load: • Simulate expected CPS (calls per second)
• Test concurrent call limits
• Verify rate limiting behaves as expected
• Monitor API response times under load