Back to Blog

Enterprise Integration Patterns: OAuth, Webhooks, and API Design for Workflow Platforms

Technical patterns and best practices for building production-grade integrations that handle authentication, rate limiting, error recovery, and webhook processing.

Enterprise Integration Patterns: OAuth, Webhooks, and API Design for Workflow Platforms
Kai Token
Kai Token
23 Apr 2025 · 5 min read

Workflow automation platforms depend on reliable integration layer connecting dozens of SaaS APIs. Each integration implements OAuth authentication, handles webhook events, respects rate limits, and provides error recovery. Building production-grade integrations requires systematic approach to API client development, credential management, and error handling.

OAuth 2.0 Implementation Patterns

Modern SaaS APIs use OAuth 2.0 for delegated authorization. Workflow platforms obtain user consent to access third-party services without handling passwords. Implement OAuth flows that handle authorization, token refresh, and token revocation.

Authorization Code Flow

Standard OAuth flow for web applications. User authorizes application via provider's authorization page. Provider redirects back with authorization code. Exchange code for access token and refresh token.

Implementation Steps:

  1. Redirect to Authorization URL: Construct authorization URL with client_id, redirect_uri, scope, and state parameter. Redirect user's browser to provider's authorization page.

  2. Handle Authorization Callback: Provider redirects to callback URL with authorization code and state parameter. Validate state matches original request to prevent CSRF attacks.

  3. Exchange Code for Token: Make POST request to token endpoint with code, client_id, client_secret, and redirect_uri. Receive access token, refresh token, and expiration time.

  4. Store Tokens Securely: Encrypt tokens before storing in database. Associate tokens with user and organization for multi-tenancy isolation.

Token Refresh Implementation

Access tokens expire after 1 hour (typical). Implement automatic token refresh to maintain integration availability.

async function refreshAccessToken(integration: Integration): Promise<string> {
  try {
    const response = await fetch(integration.tokenEndpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: integration.refreshToken,
        client_id: integration.clientId,
        client_secret: integration.clientSecret
      })
    });

    const data = await response.json();

    // Update stored tokens
    await updateIntegrationTokens(integration.id, {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt: Date.now() + (data.expires_in * 1000)
    });

    return data.access_token;
  } catch (error) {
    // Handle refresh failure - user must reauthorize
    await markIntegrationDisconnected(integration.id);
    throw new IntegrationAuthError('Token refresh failed');
  }
}

Token Revocation

Support explicit token revocation when user disconnects integration. Call revocation endpoint to invalidate tokens on provider side. Prevents orphaned access after user removes integration.

Webhook Processing Architecture

Webhooks enable real-time workflow triggers. Providers send HTTP POST requests when events occur. Workflow platform processes webhooks, validates authenticity, and triggers workflows.

Webhook Endpoint Design

Single Endpoint Per Provider: Use path parameter to identify provider: /webhooks/:provider. Route to provider-specific handler based on path.

Signature Verification: Validate webhook signatures using provider's signing secret. Prevents webhook spoofing and replay attacks.

Idempotency: Process each webhook exactly once. Store webhook IDs in database before processing. Skip processing if ID already exists.

Async Processing: Return 200 OK immediately after validating signature. Process webhook async via task queue. Prevents timeout if webhook processing is slow.

Webhook Signature Verification

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string,
  algorithm: 'sha256' | 'sha1' = 'sha256'
): boolean {
  const hmac = crypto.createHmac(algorithm, secret);
  hmac.update(payload);
  const expectedSignature = hmac.digest('hex');

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Webhook Retry Handling

Providers retry failed webhooks with exponential backoff. Handle duplicate webhooks gracefully through idempotency. Return 200 OK for duplicate processing to prevent endless retries.

Rate Limit Management

API rate limits constrain workflow execution throughput. Implement rate limit tracking, request queuing, and backoff strategies to maximize throughput while respecting limits.

Rate Limit Detection

Header Parsing: Parse rate limit headers from API responses. Common headers:

  • X-RateLimit-Limit: Total requests allowed per window
  • X-RateLimit-Remaining: Requests remaining in current window
  • X-RateLimit-Reset: Unix timestamp when window resets
  • Retry-After: Seconds to wait before retry (for 429 responses)

Proactive Throttling: Track remaining quota and slow request rate as quota approaches zero. Prevents rate limit errors.

Request Queuing

class RateLimitQueue {
  private queue: Array<QueuedRequest> = [];
  private processing = false;
  private remainingQuota: number;
  private quotaResetTime: number;

  async enqueue(request: APIRequest): Promise<APIResponse> {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, resolve, reject });
      this.processQueue();
    });
  }

  private async processQueue() {
    if (this.processing || this.queue.length === 0) return;
    this.processing = true;

    while (this.queue.length > 0) {
      // Wait if quota exhausted
      if (this.remainingQuota === 0) {
        const waitTime = this.quotaResetTime - Date.now();
        if (waitTime > 0) await sleep(waitTime);
      }

      const { request, resolve, reject } = this.queue.shift()!;

      try {
        const response = await executeRequest(request);
        this.updateRateLimitState(response.headers);
        resolve(response);
      } catch (error) {
        reject(error);
      }
    }

    this.processing = false;
  }
}

Dynamic Rate Limit Adjustment

Track actual rate limits from API responses. Providers may adjust limits dynamically based on usage patterns. Update local rate limit tracking when headers indicate limit changes.

Error Handling and Retry Logic

Integration errors fall into transient and permanent categories. Implement appropriate retry strategies for each category.

Error Classification

Transient Errors (retry with backoff):

  • 429 Too Many Requests
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable
  • 504 Gateway Timeout
  • Network timeouts
  • Connection refused

Permanent Errors (do not retry):

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 422 Unprocessable Entity

Exponential Backoff with Jitter

async function executeWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 5
): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries) throw error;

      const isTransient = isTransientError(error);
      if (!isTransient) throw error;

      // Exponential backoff: 1s, 2s, 4s, 8s, 16s
      const baseDelay = Math.pow(2, attempt) * 1000;

      // Add jitter: random 0-1000ms to prevent thundering herd
      const jitter = Math.random() * 1000;
      const delay = baseDelay + jitter;

      await sleep(delay);
    }
  }

  throw new Error('Max retries exceeded');
}

Circuit Breaker Pattern

Detect failing integrations and stop retry attempts. Prevents wasted resources on permanently failed services. Automatically reset circuit breaker after timeout.

class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime = 0;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > 60000) {
        this.state = 'HALF_OPEN';
      } else {
        throw new CircuitBreakerOpenError();
      }
    }

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

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

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

    if (this.failureCount >= 5) {
      this.state = 'OPEN';
    }
  }
}

API Client Architecture

Design API clients with testability, observability, and maintainability.

Client Interface

interface IntegrationClient {
  // Execute action with automatic auth, rate limiting, and retry
  execute(action: string, params: Record<string, any>): Promise<any>;

  // Test connection validity
  testConnection(): Promise<boolean>;

  // Refresh credentials
  refreshAuth(): Promise<void>;
}

Request Middleware

Implement middleware pattern for cross-cutting concerns:

Auth Middleware: Inject access token into requests. Handle token refresh on 401 responses.

Rate Limit Middleware: Queue requests when approaching rate limits. Parse rate limit headers and update tracking.

Retry Middleware: Implement exponential backoff for transient errors. Respect Retry-After headers.

Logging Middleware: Log request/response for debugging. Redact sensitive data (tokens, API keys).

Metrics Middleware: Track request latency, error rates, and success rates per integration.

Credential Encryption

Integration credentials require encryption at rest and in transit.

Encryption at Rest

Algorithm: AES-256-GCM for authenticated encryption. Prevents tampering and provides confidentiality.

Key Management: Store encryption keys in Key Management Service (AWS KMS, Google Cloud KMS). Rotate keys quarterly.

Field-Level Encryption: Encrypt individual credential fields rather than entire record. Enables querying non-sensitive fields.

async function encryptCredential(
  plaintext: string,
  kmsClient: KMSClient
): Promise<EncryptedCredential> {
  // Generate data encryption key (DEK)
  const dek = crypto.randomBytes(32);

  // Encrypt plaintext with DEK
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv('aes-256-gcm', dek, iv);
  const ciphertext = Buffer.concat([
    cipher.update(plaintext, 'utf8'),
    cipher.final()
  ]);
  const authTag = cipher.getAuthTag();

  // Encrypt DEK with KMS
  const encryptedDEK = await kmsClient.encrypt({ plaintext: dek });

  return {
    ciphertext: ciphertext.toString('base64'),
    encryptedDEK: encryptedDEK.toString('base64'),
    iv: iv.toString('base64'),
    authTag: authTag.toString('base64')
  };
}

Encryption in Transit

TLS 1.3: Enforce TLS 1.3 for all API communication. Disable older TLS versions.

Certificate Validation: Validate server certificates against trusted CA bundle. Prevent man-in-the-middle attacks.

Mutual TLS: For sensitive integrations, implement mutual TLS requiring client certificates.

Integration Testing

Comprehensive testing ensures integration reliability.

Unit Tests

Test integration logic without external API calls:

  • Request construction and parameter validation
  • Response parsing and error handling
  • Credential encryption and decryption
  • Rate limit tracking and throttling

Integration Tests

Test against sandbox or staging API:

  • OAuth authorization flow
  • Token refresh mechanism
  • API action execution
  • Webhook signature verification
  • Error response handling

Contract Tests

Validate API response format hasn't changed:

  • Compare response schemas against documented API spec
  • Alert on unexpected field additions or removals
  • Catch breaking API changes before production

Monitoring and Alerting

Track integration health metrics.

Key Metrics

Success Rate: Percentage of successful API requests per integration. Target: >99%.

Latency: P95 latency for API requests. Alert on >5s latency.

Error Rate: Errors per minute by type (auth, rate limit, timeout). Alert on >10 errors/minute.

Token Refresh Rate: Frequency of token refresh attempts. Spike indicates auth issues.

Webhook Processing: Webhook processing latency and success rate.

Conclusion

Production-grade integrations require systematic approach to OAuth implementation, webhook processing, rate limit management, and error handling. Implement token refresh, signature verification, request queuing, and exponential backoff to build reliable integration layer. Encrypt credentials, test comprehensively, and monitor integration health to maintain platform reliability.