Skip to main content

Overview

This guide covers all error codes, their meanings, and how to handle them properly in your BOOP Network integration. Proper error handling ensures a smooth user experience even when things go wrong.

Error Response Format

All BOOP Network errors follow a consistent format:
{
  "type": "error",
  "code": "AUTH_FAILED",
  "message": "Authentication failed: Invalid biometric data",
  "details": {
    "context_id": "ctx_abc123",
    "timestamp": "2024-01-15T10:30:00Z",
    "retry_allowed": true
  }
}

Error Categories

Authentication Errors (AUTH_*)

Errors related to user authentication and palm vein scanning:
CodeMessageCauseSolution
AUTH_FAILEDAuthentication failedPalm scan didn’t matchUser should try again
AUTH_TIMEOUTAuthentication timeoutUser took too longCreate new context
AUTH_USER_NOT_FOUNDUser not foundUser not registeredDirect to registration
AUTH_BIOMETRIC_QUALITYPoor scan qualityBad palm placementGuide user to rescan
AUTH_DEVICE_ERRORScanner malfunctionHardware issueCheck PVS device
AUTH_CONTEXT_EXPIREDContext expiredTook too long to scanCreate new context
AUTH_CONTEXT_NOT_FOUNDContext not foundInvalid context IDVerify context creation
Example Handling:
function handleAuthError(error) {
  switch (error.code) {
    case 'AUTH_FAILED':
      // Allow retry with same context
      showMessage('Authentication failed. Please try again.');
      enableRescan();
      break;

    case 'AUTH_TIMEOUT':
    case 'AUTH_CONTEXT_EXPIRED':
      // Need new context
      showMessage('Session expired. Starting new authentication...');
      createNewContext();
      break;

    case 'AUTH_USER_NOT_FOUND':
      // Redirect to registration
      showMessage('User not registered. Please sign up first.');
      redirectToRegistration();
      break;

    case 'AUTH_BIOMETRIC_QUALITY':
      // Guide user for better scan
      showMessage('Poor scan quality. Please ensure:');
      showScanningTips();
      break;

    default:
      showMessage('Authentication error. Please try again.');
      logError(error);
  }
}

Vendor Errors (VENDOR_*)

Errors related to vendor configuration and permissions:
CodeMessageCauseSolution
VENDOR_NOT_FOUNDVendor not foundInvalid vendor IDCheck credentials
VENDOR_SUSPENDEDVendor suspendedAccount suspendedContact support
VENDOR_INVALID_KEYInvalid API keyWrong/expired keyUpdate API key
VENDOR_RATE_LIMITRate limit exceededToo many requestsImplement backoff
VENDOR_PERMISSION_DENIEDPermission deniedMissing permissionsReview vendor config
VENDOR_ATTRIBUTE_DENIEDAttribute access deniedCan’t access attributeUpdate permissions
Example Handling:
class VendorErrorHandler {
  handle(error) {
    switch (error.code) {
      case 'VENDOR_INVALID_KEY':
        this.refreshApiKey();
        break;

      case 'VENDOR_RATE_LIMIT':
        const retryAfter = error.details.retry_after || 60;
        this.scheduleRetry(retryAfter);
        break;

      case 'VENDOR_ATTRIBUTE_DENIED':
        const denied = error.details.attribute;
        console.warn(`Cannot access attribute: ${denied}`);
        this.continueWithoutAttribute(denied);
        break;

      case 'VENDOR_SUSPENDED':
        this.notifyAdministrator(error);
        this.showMaintenanceMode();
        break;
    }
  }

  scheduleRetry(seconds) {
    setTimeout(() => {
      this.retryLastRequest();
    }, seconds * 1000);
  }
}

Network Errors (NET_*)

Connection and communication errors:
CodeMessageCauseSolution
NET_CONNECTION_FAILEDConnection failedCan’t reach serverCheck network
NET_TIMEOUTRequest timeoutServer didn’t respondRetry request
NET_WEBSOCKET_CLOSEDWebSocket closedConnection droppedReconnect
NET_INVALID_MESSAGEInvalid message formatMalformed dataCheck message format
NET_PROTOCOL_ERRORProtocol errorVersion mismatchUpdate SDK
Example Handling:
class NetworkErrorHandler {
  constructor(client) {
    this.client = client;
    this.reconnectAttempts = 0;
  }

  async handle(error) {
    switch (error.code) {
      case 'NET_CONNECTION_FAILED':
      case 'NET_WEBSOCKET_CLOSED':
        await this.reconnect();
        break;

      case 'NET_TIMEOUT':
        await this.retryWithBackoff();
        break;

      case 'NET_PROTOCOL_ERROR':
        console.error('Protocol version mismatch');
        await this.updateClient();
        break;
    }
  }

  async reconnect() {
    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
    console.log(`Reconnecting in ${delay}ms...`);

    await new Promise(resolve => setTimeout(resolve, delay));
    this.reconnectAttempts++;

    try {
      await this.client.connect();
      this.reconnectAttempts = 0;
      console.log('Reconnected successfully');
    } catch (error) {
      await this.handle(error);
    }
  }
}

Validation Errors (VAL_*)

Input validation and data format errors:
CodeMessageCauseSolution
VAL_MISSING_FIELDRequired field missingMissing parameterAdd required field
VAL_INVALID_FORMATInvalid formatWrong data typeFix data format
VAL_INVALID_CONTEXT_TYPEInvalid context typeUnknown typeUse valid type
VAL_INVALID_ATTRIBUTEInvalid attributeUnknown attributeCheck attribute list
VAL_AMOUNT_INVALIDInvalid amountNegative/wrong formatValidate amount
Example Handling:
function validateRequest(request) {
  const errors = [];

  // Check required fields
  const required = ['context_type', 'attributes'];
  for (const field of required) {
    if (!request[field]) {
      errors.push({
        code: 'VAL_MISSING_FIELD',
        field: field,
        message: `${field} is required`
      });
    }
  }

  // Validate context type
  const validTypes = ['payment', 'access', 'age_verification'];
  if (!validTypes.includes(request.context_type)) {
    errors.push({
      code: 'VAL_INVALID_CONTEXT_TYPE',
      field: 'context_type',
      message: `Must be one of: ${validTypes.join(', ')}`
    });
  }

  // Validate amount for payment contexts
  if (request.context_type === 'payment') {
    if (!request.metadata?.amount || request.metadata.amount <= 0) {
      errors.push({
        code: 'VAL_AMOUNT_INVALID',
        field: 'metadata.amount',
        message: 'Amount must be positive'
      });
    }
  }

  return errors;
}

System Errors (SYS_*)

Internal system errors:
CodeMessageCauseSolution
SYS_INTERNAL_ERRORInternal server errorServer issueRetry later
SYS_DATABASE_ERRORDatabase errorDB unavailableRetry later
SYS_SERVICE_UNAVAILABLEService unavailableMaintenance/outageCheck status page
SYS_MPC_FAILEDMPC operation failedComputation errorRetry request
SYS_OVERLOADEDSystem overloadedHigh loadImplement backoff
Example Handling:
class SystemErrorHandler {
  handle(error) {
    // Log all system errors
    this.logSystemError(error);

    switch (error.code) {
      case 'SYS_SERVICE_UNAVAILABLE':
        this.showMaintenanceMessage();
        this.pollForServiceRecovery();
        break;

      case 'SYS_OVERLOADED':
        const backoff = error.details.retry_after || 120;
        this.implementExponentialBackoff(backoff);
        break;

      case 'SYS_INTERNAL_ERROR':
      case 'SYS_DATABASE_ERROR':
      case 'SYS_MPC_FAILED':
        // Retry with exponential backoff
        this.retryWithBackoff(error);
        break;
    }
  }

  async pollForServiceRecovery() {
    const checkHealth = async () => {
      try {
        const response = await fetch('https://dev.app.boop.it/health');
        if (response.ok) {
          this.resumeNormalOperation();
        } else {
          setTimeout(checkHealth, 30000); // Check every 30s
        }
      } catch (error) {
        setTimeout(checkHealth, 30000);
      }
    };

    checkHealth();
  }
}

Error Recovery Strategies

1. Automatic Retry with Backoff

class RetryManager {
  constructor(maxRetries = 3, baseDelay = 1000) {
    this.maxRetries = maxRetries;
    this.baseDelay = baseDelay;
  }

  async executeWithRetry(operation, errorCodes = []) {
    let lastError;

    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error;

        // Check if error is retryable
        if (!this.isRetryable(error, errorCodes)) {
          throw error;
        }

        // Check if we've exhausted retries
        if (attempt === this.maxRetries) {
          throw new Error(`Failed after ${this.maxRetries} retries: ${error.message}`);
        }

        // Calculate delay with exponential backoff
        const delay = this.baseDelay * Math.pow(2, attempt);
        console.log(`Retry ${attempt + 1}/${this.maxRetries} after ${delay}ms`);

        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }

    throw lastError;
  }

  isRetryable(error, errorCodes) {
    // Always retry network and system errors
    const alwaysRetry = ['NET_TIMEOUT', 'SYS_INTERNAL_ERROR', 'SYS_OVERLOADED'];

    if (alwaysRetry.includes(error.code)) {
      return true;
    }

    // Check custom error codes
    if (errorCodes.length > 0) {
      return errorCodes.includes(error.code);
    }

    // Check if error explicitly allows retry
    return error.details?.retry_allowed === true;
  }
}

// Usage
const retry = new RetryManager();
const result = await retry.executeWithRetry(
  () => client.createAuthContext('payment', ['email']),
  ['NET_TIMEOUT', 'SYS_OVERLOADED']
);

2. Fallback Mechanisms

class FallbackHandler {
  async authenticateWithFallback(primaryMethod, fallbackMethod) {
    try {
      // Try primary authentication method
      return await primaryMethod();
    } catch (error) {
      console.warn('Primary auth failed:', error.message);

      // Check if fallback is appropriate
      if (this.shouldUseFallback(error)) {
        console.log('Attempting fallback authentication...');
        return await fallbackMethod();
      }

      throw error;
    }
  }

  shouldUseFallback(error) {
    const fallbackErrors = [
      'AUTH_DEVICE_ERROR',
      'SYS_MPC_FAILED',
      'NET_CONNECTION_FAILED'
    ];

    return fallbackErrors.includes(error.code);
  }
}

3. Circuit Breaker Pattern

class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = 0;
  }

  async execute(operation) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failureCount++;

    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
      console.error(`Circuit breaker opened. Will retry at ${new Date(this.nextAttempt)}`);
    }
  }
}

User-Friendly Error Messages

Map technical errors to user-friendly messages:
const errorMessages = {
  // Authentication
  'AUTH_FAILED': 'Palm scan not recognized. Please try again.',
  'AUTH_TIMEOUT': 'Authentication timed out. Please start over.',
  'AUTH_USER_NOT_FOUND': 'You need to register first. Click here to sign up.',
  'AUTH_BIOMETRIC_QUALITY': 'Please place your palm flat on the scanner.',

  // Network
  'NET_CONNECTION_FAILED': 'Connection lost. Please check your internet.',
  'NET_TIMEOUT': 'Request timed out. Please try again.',

  // Validation
  'VAL_MISSING_FIELD': 'Some information is missing. Please complete all fields.',
  'VAL_AMOUNT_INVALID': 'Invalid amount. Please check and try again.',

  // System
  'SYS_SERVICE_UNAVAILABLE': 'Service temporarily unavailable. Please try again later.',
  'SYS_OVERLOADED': 'System is busy. Please wait a moment.',

  // Default
  'DEFAULT': 'Something went wrong. Please try again.'
};

function getUserMessage(errorCode) {
  return errorMessages[errorCode] || errorMessages['DEFAULT'];
}

Error Logging

Implement comprehensive error logging:
class ErrorLogger {
  constructor(service = 'boop-integration') {
    this.service = service;
  }

  log(error, context = {}) {
    const logEntry = {
      timestamp: new Date().toISOString(),
      service: this.service,
      error: {
        code: error.code,
        message: error.message,
        details: error.details
      },
      context: {
        user_id: context.userId,
        vendor_id: context.vendorId,
        context_id: context.contextId,
        environment: process.env.BOOP_ENV
      },
      stack: error.stack
    };

    // Log to console in development
    if (process.env.BOOP_ENV === 'development') {
      console.error('Error:', logEntry);
    }

    // Send to logging service in production
    if (process.env.BOOP_ENV === 'production') {
      this.sendToLoggingService(logEntry);
    }

    // Store for debugging
    this.storeErrorForDebugging(logEntry);

    return logEntry;
  }

  sendToLoggingService(entry) {
    // Send to your logging service (e.g., Datadog, Sentry)
    // Implementation depends on your logging provider
  }

  storeErrorForDebugging(entry) {
    // Store recent errors for debugging
    if (!global.recentErrors) {
      global.recentErrors = [];
    }

    global.recentErrors.push(entry);

    // Keep only last 100 errors
    if (global.recentErrors.length > 100) {
      global.recentErrors.shift();
    }
  }
}

Testing Error Handling

// Test error handling
async function testErrorHandling() {
  const scenarios = [
    {
      name: 'Invalid API Key',
      trigger: () => new BOOPClient('invalid', 'invalid').connect(),
      expectedCode: 'VENDOR_INVALID_KEY'
    },
    {
      name: 'Network Timeout',
      trigger: () => simulateTimeout(),
      expectedCode: 'NET_TIMEOUT'
    },
    {
      name: 'Missing Required Field',
      trigger: () => client.createAuthContext(null, []),
      expectedCode: 'VAL_MISSING_FIELD'
    }
  ];

  for (const scenario of scenarios) {
    try {
      await scenario.trigger();
      console.error(`❌ ${scenario.name}: Should have thrown error`);
    } catch (error) {
      if (error.code === scenario.expectedCode) {
        console.log(`✅ ${scenario.name}: Correct error code`);
      } else {
        console.error(`❌ ${scenario.name}: Wrong error code. Got ${error.code}`);
      }
    }
  }
}

Next Steps