Skip to main content

Integration Implementation Checklist

CRITICAL: This checklist MUST be completed for EVERY new integration. Missing any step will cause the integration to fail.

Complete Implementation Checklist

When implementing a new integration, you MUST complete ALL of these steps in order:

1. Create Integration Plugin (src/lib/server/integrations/<service>.ts)

  • Create class extending BaseIntegration
  • Set id, name, description, category, icon, authType
  • Define apiKeyFields (for API key auth) or OAuth config
  • Define all metrics with proper metadata
  • Implement testConnection() method
  • Implement fetchMetrics() method
  • Register with integrationRegistry.register(new ServiceIntegration())
  • Export the class

2. Add to Integration Registry (src/lib/server/integrations/index.ts)

  • Import the integration file: import './service';
  • Uncomment if it was previously commented out
  • Add comment with status (✅ Done)

3. Add Test Connection Handler (src/convex/connections.ts)

  • Add entry to integrationTesters object
  • Implement async function that tests API credentials
  • Return { success: true, accountInfo } on success
  • Return { success: false, error: 'message' } on failure
  • Handle all error cases (401, 403, 404, etc.)
Example:
servicename: async (credentials) => {
  const { apiKey } = credentials;

  if (!apiKey) {
    return { success: false, error: 'API key is required' };
  }

  const response = await fetch('https://api.service.com/endpoint', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  if (!response.ok) {
    return {
      success: false,
      error: response.status === 401 ? 'Invalid API key' : 'Connection failed'
    };
  }

  return {
    success: true,
    accountInfo: { accountId: 'id', accountName: 'name' }
  };
},

4. Add Metrics Fetching Handler (src/convex/metrics.ts)

  • Add else if block in the sync function (around line 900-1070)
  • Check for correct credential type (connection.apiKey or connection.accessToken)
  • Parse credentials if stored as JSON
  • Import the fetch function from integration file
  • Call fetch function with proper parameters
  • Handle errors with descriptive messages
Example for API Key:
} else if (connection.service === 'servicename' && connection.apiKey) {
  try {
    const credentials = JSON.parse(connection.apiKey);
    const { fetchServiceMetrics } = await import('./integrations/servicename');
    metrics = await fetchServiceMetrics(credentials, {
      metricIds,
      startDate,
      endDate,
    });
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : 'Invalid credentials format';
    throw new Error(errorMsg);
  }
Example for OAuth:
} else if (connection.service === 'servicename' && connection.accessToken) {
  const { fetchServiceMetrics } = await import('./integrations/servicename');
  metrics = await fetchServiceMetrics(connection.accessToken, {
    metricIds,
    startDate,
    endDate,
  });

5. Add Convex Integration Config (src/convex/lib/integrationConfig.ts)

  • Add entry to INTEGRATIONS object
  • Set correct dataStrategy (snapshot, events, or hybrid)
  • Set shouldBackfill and historicalDays
  • Add user messaging
Example:
servicename: {
  id: 'servicename',
  name: 'Service Name',
  dataStrategy: { type: 'events', shouldBackfill: true, historicalDays: 90 },
  userMessaging: {
    onConnect: 'Fetching 90 days of data...',
    dataAvailability: 'Historical data available',
  },
},

6. Add Client Integration Config (src/lib/config/integrations.ts)

  • Add entry to INTEGRATION_CONFIGS object
  • Match settings with Convex config
  • Add user messaging

7. Create Convex Integration Functions (src/convex/integrations/<service>.ts)

  • Create fetchServiceMetrics() function
  • Accept credentials and options (metricIds, startDate, endDate)
  • Make API calls to fetch data
  • Transform data into metric format
  • Return array of metrics with proper structure
  • Handle pagination if needed
  • Handle rate limiting
  • Add proper error handling
Metric format:
{
  metricId: string;
  metricName: string;
  value: number;
  unit?: string;
  timestamp: number;
  metadata?: Record<string, unknown>;
}

8. Update Integration Roadmap (notes/INTEGRATION_ROADMAP.md)

  • Add integration to appropriate category
  • Mark as ✅ Done
  • Update total count
  • Add notes about auth type, backfill, etc.
  • Add SVG icon to static/integrations/<service>.svg
  • Use simple, recognizable logo
  • Optimize SVG file size

Common Mistakes to Avoid

  1. Forgetting to import in index.ts - Integration won’t appear in UI
  2. Not adding to integrationTesters - Test connection will fail
  3. Not adding to metrics.ts sync function - Metrics won’t sync
  4. Wrong credential parsing - API key stored as JSON needs JSON.parse()
  5. Missing error handling - Sync will fail silently
  6. Not testing with real credentials - Integration may work in theory but fail in practice

Testing Checklist

After implementation, test these scenarios:
  • Integration appears on /integrations page
  • Test connection succeeds with valid credentials
  • Test connection fails gracefully with invalid credentials
  • Initial sync fetches historical data (if backfill enabled)
  • Metrics show non-zero values (if data exists)
  • Subsequent syncs work correctly
  • Error messages are clear and helpful
  • Integration shows in correct category

Quick Reference: Files to Update

Every integration requires changes to these files:
  1. src/lib/server/integrations/<service>.ts - NEW FILE
  2. src/lib/server/integrations/index.ts - ADD IMPORT
  3. src/convex/connections.ts - ADD TO integrationTesters
  4. src/convex/metrics.ts - ADD TO sync function
  5. src/convex/integrations/<service>.ts - NEW FILE
  6. src/convex/lib/integrationConfig.ts - ADD CONFIG
  7. src/lib/config/integrations.ts - ADD CONFIG
  8. notes/INTEGRATION_ROADMAP.md - UPDATE STATUS

Integration Template

Use this as a starting point for new integrations:
// src/lib/server/integrations/servicename.ts
import { BaseIntegration } from './BaseIntegration';
import { integrationRegistry } from './registry';
import type { IntegrationMetric } from './types';

class ServiceNameIntegration extends BaseIntegration {
  id = 'servicename';
  name = 'Service Name';
  description = 'Description of the service';
  category = 'category' as const; // payment, development, analytics, communication, marketing
  icon = '/integrations/servicename.svg';
  authType = 'api_key' as const; // or 'oauth'

  apiKeyFields = [
    {
      name: 'apiKey',
      label: 'API Key',
      placeholder: 'sk_...',
      type: 'password' as const,
      required: true,
      helpText: 'Find in Settings > API Keys',
    },
  ];

  metrics: IntegrationMetric[] = [
    {
      id: 'servicename_metric',
      name: 'Metric Name',
      description: 'Metric description',
      unit: 'count',
      category: 'category',
      defaultVisualization: 'metric-card',
      supportedVisualizations: ['metric-card', 'line-chart'],
      isTimeSeries: true,
    },
  ];

  async testConnection(credentials: Record<string, string>) {
    try {
      const response = await fetch('https://api.service.com/test', {
        headers: { 'Authorization': `Bearer ${credentials.apiKey}` }
      });

      if (!response.ok) {
        return {
          success: false,
          error: response.status === 401 ? 'Invalid API key' : 'Connection failed',
        };
      }

      return {
        success: true,
        accountInfo: {
          accountId: 'id',
          accountName: 'name',
        },
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : 'Connection failed',
      };
    }
  }

  async fetchMetrics(
    credentials: Record<string, string>,
    options: { metricIds: string[]; startDate: Date; endDate: Date }
  ) {
    const metrics: Array<{
      metricId: string;
      value: number;
      timestamp: number;
      metadata?: Record<string, unknown>;
    }> = [];

    // Implement metric fetching logic here

    return metrics;
  }
}

integrationRegistry.register(new ServiceNameIntegration());
export { ServiceNameIntegration };

Notes

  • Always test with real credentials before marking as complete
  • Document any API limitations or quirks
  • Add helpful error messages for common failure cases
  • Consider rate limits and add appropriate delays if needed
  • Use proper TypeScript types throughout