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:
-
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.
-
Handle Authorization Callback: Provider redirects to callback URL with authorization code and state parameter. Validate state matches original request to prevent CSRF attacks.
-
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.
-
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 windowX-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when window resetsRetry-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.