Skip to content

Error Handling

Handle errors gracefully and provide clear feedback to users.

Error Response Format

All WalletGate API errors follow this consistent structure:

json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "field": "fieldName",  // Optional: which field caused the error
    "details": {           // Optional: additional context
      "key": "value"
    }
  }
}

HTTP Status Codes

CodeMeaningWhen It Happens
200SuccessRequest completed successfully
201CreatedResource created (e.g., new session)
400Bad RequestInvalid input or validation error
401UnauthorizedMissing or invalid API key
403ForbiddenQuota exceeded or insufficient permissions
404Not FoundResource doesn't exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorSomething went wrong on our end

Common Errors

Authentication Errors

Missing API Key

json
{
  "success": false,
  "error": {
    "code": "MISSING_API_KEY",
    "message": "No API key provided. Include Authorization header with Bearer token."
  }
}

Fix:

typescript
// ✅ Correct
headers: {
  'Authorization': `Bearer ${apiKey}`,
}

// ❌ Wrong - missing header
headers: {}

Invalid API Key

json
{
  "success": false,
  "error": {
    "code": "INVALID_API_KEY",
    "message": "API key is invalid or has been revoked."
  }
}

Causes:

  • Key was deleted
  • Typo in key
  • Key format incorrect

Fix: Verify key in dashboard

Validation Errors

Invalid Check Type

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid check type",
    "field": "checks[0].type",
    "details": {
      "allowed": ["age_over", "residency_eu", "identity_verified"]
    }
  }
}

Fix:

typescript
// ✅ Correct
checks: [{ type: 'age_over', value: 18 }]

// ❌ Wrong
checks: [{ type: 'age_check', value: 18 }]

Missing Required Field

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "checks is required",
    "field": "checks"
  }
}

Invalid URL Format

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid redirect URL",
    "field": "redirectUrl",
    "details": {
      "value": "not-a-url",
      "expected": "Valid HTTPS URL"
    }
  }
}

Quota Errors

Monthly Limit Exceeded

json
{
  "success": false,
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Monthly verification quota exceeded. Upgrade your plan or wait for reset.",
    "details": {
      "used": 1000,
      "limit": 1000,
      "resetAt": "2024-02-01T00:00:00Z",
      "upgradeUrl": "https://console.walletgate.app/admin"
    }
  }
}

Fix: Upgrade plan or wait for monthly reset

Rate Limit Errors

Too Many Requests

json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please slow down.",
    "details": {
      "limit": 10,
      "remaining": 0,
      "resetAt": 1704067260000
    }
  }
}

Headers included:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067260

Fix: Implement exponential backoff

Resource Errors

Session Not Found

json
{
  "success": false,
  "error": {
    "code": "SESSION_NOT_FOUND",
    "message": "Verification session not found or expired."
  }
}

Causes:

  • Session ID doesn't exist
  • Session expired (15 min timeout)
  • Typo in session ID

Verification Errors

User Declined

json
{
  "success": false,
  "error": {
    "code": "USER_DECLINED",
    "message": "User cancelled verification in wallet."
  }
}

Session Expired

json
{
  "success": false,
  "error": {
    "code": "SESSION_EXPIRED",
    "message": "Verification session expired after 15 minutes."
  }
}

Invalid Credentials

json
{
  "success": false,
  "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "Certificate validation failed",
    "details": {
      "reason": "Certificate revoked by issuer",
      "issuer": "DE Trust Service Provider"
    }
  }
}

Error Handling Patterns

Basic Try-Catch

typescript
import { WalletGate, WalletGateApiError } from '@walletgate/eudi';

const client = new WalletGate({
  apiKey: process.env.WALLETGATE_API_KEY,
});

try {
  const session = await client.startVerification({
    checks: [{ type: 'age_over', value: 18 }],
  });

  console.log('Session created:', session.id);
} catch (error) {
  if (error instanceof WalletGateApiError) {
    switch (error.code) {
      case 'RATE_LIMIT_EXCEEDED':
        console.error('Rate limit exceeded. Please wait or upgrade.');
        break;
      case 'UNAUTHORIZED':
        console.error('Invalid API key. Check configuration.');
        break;
      case 'VALIDATION_ERROR':
        console.error('Invalid request. Check your payload.');
        break;
      default:
        console.error(`API error: ${error.message} (${error.code || 'UNKNOWN'})`);
    }
    console.error('Details:', error.details);
  } else {
    console.error('Network error:', (error as any)?.message || String(error));
  }
}

Retry with Exponential Backoff

typescript
async function createSessionWithRetry(
  checks: any[],
  maxRetries: number = 3
): Promise<VerificationSession> {
  let lastError: Error;

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

      // Don't retry on validation errors or quota errors
      if (error instanceof WalletGateApiError && (error.code === 'VALIDATION_ERROR' || error.code === 'RATE_LIMIT_EXCEEDED')) {
        throw error;
      }

      // Exponential backoff
      const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
      console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError;
}

User-Friendly Error Messages

typescript
function getUserFriendlyErrorMessage(error: unknown): string {
  const errorCode = error instanceof WalletGateApiError ? error.code : undefined;

  const messages: Record<string, string> = {
    RATE_LIMIT_EXCEEDED: 'We\'ve reached our verification limit. Please try again later or upgrade.',
    USER_DECLINED: 'Verification was cancelled. Please try again when you\'re ready.',
    SESSION_EXPIRED: 'Verification link expired. Click below to generate a new one.',
    INVALID_CREDENTIALS: 'We couldn\'t verify your credentials. Please ensure your wallet is up to date.',
    UNAUTHORIZED: 'Authentication failed. Check your API key.',
  };

  return messages[errorCode] || 'Something went wrong. Please try again or contact support.';
}

// Usage in UI
try {
  await createSession();
} catch (error) {
  showErrorToast(getUserFriendlyErrorMessage(error));
}

Frontend Error Handling

React Example

typescript
import { useState } from 'react';

function VerificationButton() {
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleVerify = async () => {
    setError(null);
    setLoading(true);

    try {
      const response = await fetch('/api/verify/create', {
        method: 'POST',
      });

      if (!response.ok) {
        const data = await response.json();
        throw new Error(data.error || data.message || 'Request failed');
      }

      const { sessionId } = await response.json();
      // Show QR code...
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handleVerify} disabled={loading}>
        {loading ? 'Creating session...' : 'Verify Age'}
      </button>

      {error && (
        <div className="error-message">
          ⚠️ {error}
        </div>
      )}
    </div>
  );
}

Logging Best Practices

What to Log

typescript
// ✅ Log these
console.error('Verification failed:', {
  sessionId: session.id,
  errorCode: error.code,
  timestamp: new Date().toISOString(),
  userId: metadata.userId, // If applicable
});

// ❌ Don't log these
console.error('Error:', {
  apiKey: process.env.WALLETGATE_API_KEY, // NEVER log API keys!
  rawCredentials: credentials, // NEVER log PII!
});

Structured Logging

typescript
import { WalletGateApiError } from '@walletgate/eudi';
import pino from 'pino';

const logger = pino();

try {
  await client.startVerification({ checks });
} catch (error) {
  if (!(error instanceof WalletGateApiError)) throw error;
  logger.error({
    msg: 'Session creation failed',
    errorCode: error.code,
    errorMessage: error.message,
    endpoint: '/v1/verify/sessions',
    method: 'POST',
    timestamp: Date.now(),
  });
}

Monitoring & Alerts

Set Up Alerts

Monitor these error rates:

  • High priority:

    • Authentication errors > 5%
    • Internal server errors > 0.1%
    • Quota exceeded (upgrade needed)
  • Medium priority:

    • Validation errors > 10%
    • Rate limit errors > 1%
  • Low priority:

    • User declined > 20%
    • Session expired > 10%

Error Rate Calculation

typescript
const errorRate = (failedRequests / totalRequests) * 100;

if (errorRate > 5) {
  alertTeam({
    message: `High error rate: ${errorRate}%`,
    severity: 'high',
    details: {
      totalRequests,
      failedRequests,
      timeWindow: '5 minutes',
    },
  });
}

Complete Error Code Reference

CodeHTTPDescriptionAction
UNAUTHORIZED401Missing/invalid API keyCheck dashboard / config
RATE_LIMIT_EXCEEDED429Too many requestsImplement backoff
VALIDATION_ERROR400Invalid inputFix request body
NOT_FOUND404Session doesn't existCheck session ID
REDIRECT_ALLOWLIST_REQUIRED400Redirect allowlist not configuredConfigure allowlist in dashboard
REDIRECT_ORIGIN_NOT_ALLOWED400Redirect origin not allowlistedAdd origin to allowlist
IDEMPOTENCY_KEY_INVALID400Invalid Idempotency-Key headerFix key (1-256 printable ASCII)
IDEMPOTENCY_CONFLICT409Same key is still processingRetry after 1s
IDEMPOTENCY_PAYLOAD_MISMATCH422Same key used with different bodyUse a new key
INTERNAL_ERROR500Server errorRetry, contact support

View interactive API reference →

Testing Error Scenarios

Force Errors in Test Mode

typescript
// Force validation error
await client.startVerification({
  checks: [{ type: 'invalid_type', value: 18 }], // Wrong type
});

// Force session not found
await client.getResult('00000000-0000-0000-0000-000000000000');

Simulate Network Errors

typescript
// Simulate timeout
const client = new WalletGate({
  apiKey: process.env.WALLETGATE_API_KEY,
  timeout: 1, // 1ms timeout - will always fail
});

// Simulate network error
// (disconnect internet, use invalid base URL, etc.)

Next Steps

Need Help?

Developer-first EU Digital Identity verification