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('/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