Skip to content

❌ Error Handling & Troubleshooting Guide

📋 Overview

This guide provides comprehensive error handling strategies for Partner Services API integration, including common error scenarios, troubleshooting steps, and best practices for robust error management.

Coverage:

  • Authentication Errors - HMAC, Partner ID, Timestamp issues
  • API Errors - Rate limits, validation, service unavailable
  • Network Errors - Timeouts, connectivity, DNS issues
  • Business Logic Errors - Insufficient coverage, invalid data
  • Recovery Strategies - Retry logic, fallback mechanisms

🔍 Error Categories

1. Authentication Errors (401)

AUTHENTICATION_FAILED - Invalid HMAC signature

json
{
  "success": false,
  "error": {
    "code": "AUTHENTICATION_FAILED",
    "message": "Invalid HMAC signature or partner credentials",
    "details": {
      "reason": "SIGNATURE_MISMATCH",
      "partnerId": "partner_001",
      "timestamp": 1642752480
    }
  }
}

Common Causes:

  • Incorrect secret key
  • Wrong string-to-sign format
  • JSON formatting inconsistency
  • Extra whitespace in request body

Solutions:

javascript
// Debug signature generation
function debugSignature(method, path, body, partnerId, secretKey) {
  const timestamp = Math.floor(Date.now() / 1000);
  const bodyStr = body ? JSON.stringify(body) : "";
  const stringToSign = `${method}\n${path}\n${bodyStr}\n${timestamp}`;

  console.log("Debug Info:");
  console.log("String to Sign:", JSON.stringify(stringToSign));
  console.log("Body String:", bodyStr);
  console.log("Secret Key Length:", secretKey.length);

  const signature = crypto
    .createHmac("sha256", secretKey)
    .update(stringToSign)
    .digest("hex");

  return signature;
}

PARTNER_NOT_FOUND

json
{
  "success": false,
  "error": {
    "code": "PARTNER_NOT_FOUND",
    "message": "Partner ID not found or inactive"
  }
}

Solutions:

  • Verify partner ID is correct
  • Check partner account status
  • Ensure API access is enabled

TIMESTAMP_INVALID

json
{
  "success": false,
  "error": {
    "code": "TIMESTAMP_INVALID",
    "message": "Request timestamp is too old or too far in the future"
  }
}

Solutions:

javascript
// Ensure timestamp is current
const timestamp = Math.floor(Date.now() / 1000);

// Check system clock synchronization
const serverTime = await getServerTime();
const clockDrift = Math.abs(Date.now() - serverTime);
if (clockDrift > 30000) {
  // 30 seconds
  console.warn("System clock may be out of sync");
}

2. Rate Limiting Errors (429)

RATE_LIMIT_EXCEEDED

json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Request rate limit exceeded for your tier",
    "details": {
      "tier": "TIER_1",
      "limit": 1000,
      "window": "60 seconds",
      "resetTime": "2025-07-22T10:49:00.000Z",
      "retryAfter": 15
    }
  }
}

Rate Limit Headers:

http
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1642752540
Retry-After: 15

Handling Strategy:

javascript
class RateLimitHandler {
  async makeRequestWithRetry(requestFn, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await requestFn();
      } catch (error) {
        if (error.code === "RATE_LIMIT_EXCEEDED") {
          const retryAfter = error.details?.retryAfter || attempt * 2;

          if (attempt < maxRetries) {
            console.log(`Rate limited. Retrying in ${retryAfter} seconds...`);
            await this.sleep(retryAfter * 1000);
            continue;
          } else {
            throw new Error("Rate limit exceeded. Max retries reached.");
          }
        }
        throw error; // Re-throw non-rate-limit errors
      }
    }
  }

  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

3. Validation Errors (400)

VALIDATION_ERROR

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": {
      "validationErrors": [
        {
          "field": "pickupPincode",
          "value": "invalid",
          "issue": "Must be a valid 6-digit pincode",
          "expectedFormat": "NNNNNN (6 digits)"
        },
        {
          "field": "weight",
          "value": -100,
          "issue": "Weight must be greater than 0",
          "constraints": {
            "min": 1,
            "max": 50000,
            "unit": "grams"
          }
        }
      ]
    }
  }
}

Validation Helper:

javascript
class ShipmentValidator {
  static validate(shipmentDetails) {
    const errors = [];

    // Pincode validation
    if (
      !shipmentDetails.pickupPincode ||
      !/^\d{6}$/.test(shipmentDetails.pickupPincode)
    ) {
      errors.push({
        field: "pickupPincode",
        message: "Must be a valid 6-digit pincode",
      });
    }

    if (
      !shipmentDetails.deliveryPincode ||
      !/^\d{6}$/.test(shipmentDetails.deliveryPincode)
    ) {
      errors.push({
        field: "deliveryPincode",
        message: "Must be a valid 6-digit pincode",
      });
    }

    // Weight validation
    if (
      !shipmentDetails.weight ||
      shipmentDetails.weight <= 0 ||
      shipmentDetails.weight > 50000
    ) {
      errors.push({
        field: "weight",
        message: "Weight must be between 1g and 50kg",
      });
    }

    // Shipment type validation
    const validTypes = ["B2B", "B2C", "C2C"];
    if (
      !shipmentDetails.shipmentType ||
      !validTypes.includes(shipmentDetails.shipmentType)
    ) {
      errors.push({
        field: "shipmentType",
        message: `Must be one of: ${validTypes.join(", ")}`,
      });
    }

    return {
      isValid: errors.length === 0,
      errors,
    };
  }
}

// Usage
const validation = ShipmentValidator.validate(shipmentDetails);
if (!validation.isValid) {
  throw new ValidationError("Invalid shipment details", validation.errors);
}

4. Service Unavailable Errors (422/503)

INSUFFICIENT_COVERAGE

json
{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_COVERAGE",
    "message": "Partner does not have coverage for requested route",
    "details": {
      "partnerId": "partner_001",
      "pickupPincode": "110001",
      "deliveryPincode": "999999",
      "coverage": {
        "pickup": {
          "available": true,
          "zone": "ZN001"
        },
        "delivery": {
          "available": false,
          "reason": "PINCODE_NOT_SERVICEABLE",
          "suggestedAlternatives": ["999998", "999997"]
        }
      }
    }
  }
}

Handling Strategy:

javascript
class CoverageHandler {
  async findAlternativePartners(shipmentDetails, originalError) {
    try {
      // Try to find other partners
      const alternatives = await this.searchAlternativePartners(
        shipmentDetails
      );

      if (alternatives.length > 0) {
        return {
          success: true,
          alternatives,
          message: `Found ${alternatives.length} alternative partners`,
        };
      }

      // Check for nearby pincodes
      const nearbyOptions = await this.findNearbyServiceablePincodes(
        shipmentDetails.deliveryPincode
      );

      return {
        success: false,
        suggestions: nearbyOptions,
        originalError,
      };
    } catch (error) {
      return {
        success: false,
        message: "No alternatives found",
        originalError,
      };
    }
  }
}

SERVICE_TEMPORARILY_UNAVAILABLE

json
{
  "success": false,
  "error": {
    "code": "SERVICE_TEMPORARILY_UNAVAILABLE",
    "message": "Service is temporarily unavailable",
    "details": {
      "retryAfter": 300,
      "estimatedRecovery": "2025-07-22T11:00:00.000Z"
    }
  }
}

5. Network Errors

Connection Timeout

javascript
class NetworkErrorHandler {
  async makeRequestWithTimeout(requestFn, timeoutMs = 30000) {
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error("Request timeout")), timeoutMs);
    });

    try {
      return await Promise.race([requestFn(), timeoutPromise]);
    } catch (error) {
      if (error.message === "Request timeout") {
        throw new NetworkError("Request timed out", { timeout: timeoutMs });
      }
      throw error;
    }
  }
}

DNS Resolution Failure

javascript
// Fallback URL strategy
class URLFailoverHandler {
  constructor(baseURLs) {
    this.baseURLs = baseURLs;
    this.currentIndex = 0;
  }

  async makeRequest(path, options = {}) {
    let lastError;

    for (let i = 0; i < this.baseURLs.length; i++) {
      const url = this.baseURLs[this.currentIndex];

      try {
        const response = await fetch(`${url}${path}`, options);
        return response;
      } catch (error) {
        lastError = error;
        console.warn(`Request failed for ${url}: ${error.message}`);

        // Try next URL
        this.currentIndex = (this.currentIndex + 1) % this.baseURLs.length;
      }
    }

    throw new NetworkError("All endpoints failed", { lastError });
  }
}

🔄 Comprehensive Error Handler

Universal Error Handler Implementation

javascript
class PartnerServicesErrorHandler {
  constructor(options = {}) {
    this.maxRetries = options.maxRetries || 3;
    this.baseDelay = options.baseDelay || 1000;
    this.maxDelay = options.maxDelay || 30000;
    this.retryableErrors = options.retryableErrors || [
      "RATE_LIMIT_EXCEEDED",
      "SERVICE_TEMPORARILY_UNAVAILABLE",
      "NETWORK_ERROR",
      "TIMEOUT",
    ];
  }

  async executeWithErrorHandling(operation, context = {}) {
    let lastError;
    let attempt = 0;

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

        console.error(`Attempt ${attempt} failed:`, {
          error: error.message,
          code: error.code,
          context,
        });

        if (attempt > this.maxRetries) {
          break;
        }

        if (!this.isRetryableError(error)) {
          break;
        }

        const delay = this.calculateDelay(attempt, error);
        console.log(`Retrying in ${delay}ms...`);
        await this.sleep(delay);
      }
    }

    // All retries exhausted
    throw this.createFinalError(lastError, attempt - 1, context);
  }

  isRetryableError(error) {
    return (
      this.retryableErrors.includes(error.code) ||
      error.name === "NetworkError" ||
      error.name === "TimeoutError"
    );
  }

  calculateDelay(attempt, error) {
    // Exponential backoff with jitter
    let delay = Math.min(
      this.baseDelay * Math.pow(2, attempt - 1),
      this.maxDelay
    );

    // Add jitter (±25%)
    const jitter = delay * 0.25 * (Math.random() - 0.5);
    delay += jitter;

    // Respect server retry-after header
    if (error.details?.retryAfter) {
      delay = Math.max(delay, error.details.retryAfter * 1000);
    }

    return Math.round(delay);
  }

  createFinalError(originalError, attempts, context) {
    return new Error(
      `Operation failed after ${attempts} retries. ` +
        `Last error: ${originalError.message}`,
      {
        cause: originalError,
        attempts,
        context,
      }
    );
  }

  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

// Usage
const errorHandler = new PartnerServicesErrorHandler({
  maxRetries: 3,
  baseDelay: 1000,
});

const result = await errorHandler.executeWithErrorHandling(
  () =>
    client.makeRequest("POST", "/shipments/calculate-charges", shipmentData),
  { operation: "calculate_charges", shipmentId: "SHP_001" }
);

🛠️ Error Recovery Strategies

1. Circuit Breaker Pattern

javascript
class CircuitBreaker {
  constructor(options = {}) {
    this.threshold = options.threshold || 5;
    this.timeout = options.timeout || 60000;
    this.monitor = options.monitor || false;

    this.state = "CLOSED";
    this.failureCount = 0;
    this.lastFailure = null;
    this.successCount = 0;
  }

  async execute(operation) {
    if (this.state === "OPEN") {
      if (Date.now() - this.lastFailure < this.timeout) {
        throw new Error("Circuit breaker is OPEN");
      } else {
        this.state = "HALF_OPEN";
      }
    }

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

  onSuccess() {
    this.failureCount = 0;
    this.successCount++;

    if (this.state === "HALF_OPEN") {
      this.state = "CLOSED";
    }
  }

  onFailure() {
    this.failureCount++;
    this.lastFailure = Date.now();

    if (this.failureCount >= this.threshold) {
      this.state = "OPEN";
    }
  }
}

2. Fallback Data Strategy

javascript
class FallbackDataProvider {
  constructor() {
    this.cache = new Map();
    this.fallbackData = new Map();
  }

  async getPartnerDataWithFallback(partnerId) {
    try {
      // Try primary API
      const data = await client.makeRequest(
        "GET",
        `/partners/comprehensive-data`
      );
      this.cache.set(partnerId, data);
      return data;
    } catch (error) {
      console.warn("Primary API failed, trying cache...");

      // Try cache
      const cachedData = this.cache.get(partnerId);
      if (cachedData) {
        return cachedData;
      }

      // Use fallback data
      console.warn("Cache miss, using fallback data...");
      const fallbackData = this.fallbackData.get(partnerId);
      if (fallbackData) {
        return fallbackData;
      }

      throw new Error(
        "No data available (primary API, cache, and fallback all failed)"
      );
    }
  }

  setFallbackData(partnerId, data) {
    this.fallbackData.set(partnerId, data);
  }
}

📊 Error Monitoring & Logging

Error Logger Implementation

javascript
class ErrorLogger {
  constructor(options = {}) {
    this.logLevel = options.logLevel || "ERROR";
    this.enableConsole = options.enableConsole !== false;
    this.enableRemote = options.enableRemote || false;
    this.remoteEndpoint = options.remoteEndpoint;
  }

  async logError(error, context = {}) {
    const errorLog = {
      timestamp: new Date().toISOString(),
      level: "ERROR",
      message: error.message,
      code: error.code,
      stack: error.stack,
      context,
      environment: process.env.NODE_ENV,
      userId: context.userId,
      sessionId: context.sessionId,
      requestId: context.requestId,
    };

    if (this.enableConsole) {
      console.error("API Error:", errorLog);
    }

    if (this.enableRemote && this.remoteEndpoint) {
      try {
        await fetch(this.remoteEndpoint, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(errorLog),
        });
      } catch (logError) {
        console.error("Failed to send error log:", logError);
      }
    }
  }

  async logWarning(message, context = {}) {
    if (this.enableConsole) {
      console.warn("API Warning:", {
        message,
        context,
        timestamp: new Date().toISOString(),
      });
    }
  }
}

🎯 Testing Error Scenarios

Error Simulation for Testing

javascript
class ErrorSimulator {
  constructor(client) {
    this.client = client;
    this.errorScenarios = new Map();
  }

  simulateAuthenticationError() {
    // Simulate invalid signature
    const originalMakeRequest = this.client.makeRequest;
    this.client.makeRequest = async (method, path, body) => {
      throw new Error("AUTHENTICATION_FAILED: Invalid HMAC signature");
    };
  }

  simulateRateLimit() {
    let requestCount = 0;
    const originalMakeRequest = this.client.makeRequest;

    this.client.makeRequest = async (method, path, body) => {
      requestCount++;
      if (requestCount > 5) {
        const error = new Error("RATE_LIMIT_EXCEEDED");
        error.details = { retryAfter: 10 };
        throw error;
      }
      return originalMakeRequest.call(this.client, method, path, body);
    };
  }

  simulateNetworkTimeout() {
    const originalMakeRequest = this.client.makeRequest;
    this.client.makeRequest = async (method, path, body) => {
      await new Promise((resolve) => setTimeout(resolve, 35000)); // 35 second delay
      return originalMakeRequest.call(this.client, method, path, body);
    };
  }

  restoreNormalOperation() {
    // Reset to original behavior
    delete this.client.makeRequest;
  }
}

// Usage in tests
const simulator = new ErrorSimulator(client);
simulator.simulateRateLimit();

// Test error handling
try {
  for (let i = 0; i < 10; i++) {
    await client.makeRequest("GET", "/health");
  }
} catch (error) {
  console.log("Expected rate limit error:", error.message);
}

simulator.restoreNormalOperation();

🚨 Emergency Response Procedures

Service Degradation Response

javascript
class EmergencyResponseHandler {
  constructor() {
    this.degradationMode = false;
    this.essentialEndpoints = ["/health", "/partners/availability/check"];
  }

  enableDegradationMode() {
    this.degradationMode = true;
    console.warn("🚨 Emergency: Service degradation mode enabled");
  }

  async handleRequest(method, path, body) {
    if (this.degradationMode) {
      // Only allow essential endpoints
      if (!this.isEssentialEndpoint(path)) {
        throw new Error(
          "Service temporarily unavailable. Only essential operations allowed."
        );
      }

      // Use cached data when possible
      const cachedResponse = await this.getCachedResponse(method, path, body);
      if (cachedResponse) {
        console.warn("Using cached response due to service degradation");
        return cachedResponse;
      }
    }

    return await this.client.makeRequest(method, path, body);
  }

  isEssentialEndpoint(path) {
    return this.essentialEndpoints.some((endpoint) =>
      path.startsWith(endpoint)
    );
  }
}

📋 Error Handling Checklist

Implementation Checklist

  • [ ] Authentication Error Handling

    • [ ] HMAC signature validation
    • [ ] Partner ID verification
    • [ ] Timestamp validation
    • [ ] Secret key rotation support
  • [ ] Rate Limiting

    • [ ] Respect retry-after headers
    • [ ] Exponential backoff implementation
    • [ ] Request queuing for burst scenarios
  • [ ] Network Resilience

    • [ ] Connection timeout handling
    • [ ] DNS failover support
    • [ ] Circuit breaker implementation
  • [ ] Business Logic Errors

    • [ ] Coverage validation
    • [ ] Alternative partner suggestions
    • [ ] Graceful degradation strategies
  • [ ] Monitoring & Alerting

    • [ ] Error rate monitoring
    • [ ] Response time tracking
    • [ ] Critical error alerting

🎯 Next Steps

  1. Authentication Guide - Prevent auth errors
  2. Main System Flow - Robust implementation
  3. API Examples - Error handling examples

Robust error handling ensures your integration remains stable and provides excellent user experience even when issues occur! ❌→✅

Released under the MIT License.