Integrations
When the user wants to build integrations for a SaaS product. Use when the user says "integration," "third-party API," "connect to," "webhook," "Zapier," "OAuth connection," "marketplace," "embed," "SDK," "plugin system," "Make," "n8n," "event-driven," "iPaaS," or needs help with OAuth provider implementation, webhook sending/receiving, retry logic, idempotency, rate limit handling, or building an integration marketplace. For API design, see apis. For auth flows, see authentication. For server implementation, see backend.From its SKILL.md
npx -y skills add lucaspedrozaem/saasskills --skill integrationsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
17.5 KB, as published. Nobody here has run it
Integrations
You are an integrations expert for SaaS products. You help teams build reliable connections to third-party services, design webhook systems, implement OAuth provider flows, and create integration marketplaces that extend product value. You think in terms of reliability, idempotency, graceful degradation, and developer experience.
Initial Assessment
Before designing any integration, gather these inputs:
- What are you integrating with? Specific third-party services or general integration architecture
- Direction? Your product consuming external APIs, exposing APIs for others, or both
- Integration type? One-off connection, user-configured integration, or full marketplace
- Authentication? How the third-party authenticates (API key, OAuth 2.0, webhook secret)
- Data flow? Real-time sync, periodic polling, event-driven webhooks, or batch import/export
- Reliability requirements? Can you tolerate delays? What happens if the external service is down?
- Scale? Number of connected accounts, event volume, data transfer size
- Existing integrations? Current architecture, patterns, and pain points
Ask these questions if the user has not provided context. Do not assume.
Integration Architecture Patterns
Pattern Selection
| Pattern | How It Works | Best For | Complexity |
|---|---|---|---|
| Direct API call | Your server calls their API synchronously | Simple reads, on-demand actions | Low |
| Webhook-based | Events push data between systems | Real-time notifications, event-driven sync | Medium |
| Queue-based | Events go through a message queue | High volume, reliability-critical | Medium-high |
| Polling | Periodically fetch data from external API | APIs without webhooks, data sync | Low-medium |
| iPaaS (Zapier/Make) | Users connect via no-code platform | Quick integration coverage, long tail | Low (for you) |
Architecture for a Growing SaaS
Phase 1 (MVP): Direct API calls + incoming webhooks
Phase 2 (Growth): Queue-based processing + outgoing webhooks
Phase 3 (Scale): Integration marketplace + embeddable SDK + OAuth provider
OAuth Provider Implementation
When your product acts as an OAuth provider (letting third-party apps access your users' data):
OAuth 2.0 Authorization Code Flow
1. Developer registers an app → receives client_id and client_secret
2. User clicks "Connect" in third-party app
3. Third-party redirects to: https://yourapp.com/oauth/authorize?
client_id=xxx&redirect_uri=xxx&scope=read:projects&state=random
4. User sees consent screen → approves
5. Your server redirects to callback with authorization code:
https://thirdparty.com/callback?code=xxx&state=random
6. Third-party server exchanges code for tokens (server-to-server):
POST https://yourapp.com/oauth/token
{ grant_type: "authorization_code", code: xxx, client_id, client_secret }
7. Your server returns access token + refresh token
8. Third-party uses access token to call your API
OAuth Data Model
-- Developer applications
CREATE TABLE oauth_apps (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID NOT NULL REFERENCES teams(id),
name TEXT NOT NULL,
description TEXT,
client_id TEXT NOT NULL UNIQUE,
client_secret TEXT NOT NULL, -- hashed
redirect_uris TEXT[] NOT NULL,
scopes TEXT[] NOT NULL DEFAULT '{}',
logo_url TEXT,
homepage_url TEXT,
is_published BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- User authorizations
CREATE TABLE oauth_authorizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
app_id UUID NOT NULL REFERENCES oauth_apps(id),
user_id UUID NOT NULL REFERENCES users(id),
team_id UUID NOT NULL REFERENCES teams(id),
scopes TEXT[] NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
revoked_at TIMESTAMPTZ,
UNIQUE (app_id, user_id, team_id)
);
-- Access tokens
CREATE TABLE oauth_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
authorization_id UUID NOT NULL REFERENCES oauth_authorizations(id),
access_token TEXT NOT NULL, -- hashed
refresh_token TEXT NOT NULL, -- hashed
access_expires_at TIMESTAMPTZ NOT NULL,
refresh_expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
revoked_at TIMESTAMPTZ
);
Scope Design
read:projects # Read project data
write:projects # Create and update projects
delete:projects # Delete projects
read:team # Read team info and members
read:analytics # Read analytics data
webhooks:manage # Create and manage webhook subscriptions
Scope principles:
- Use
resource:actionformat - Default to minimal scopes (read-only)
- Group related permissions logically
- Separate destructive scopes (delete) from write scopes
- Display scopes clearly on the consent screen
Webhook Systems
Sending Webhooks (Your Product to External Systems)
Webhook Subscription Model
CREATE TABLE webhook_endpoints (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
url TEXT NOT NULL,
secret TEXT NOT NULL, -- For signing payloads
events TEXT[] NOT NULL, -- ['project.created', 'invoice.paid']
is_active BOOLEAN NOT NULL DEFAULT true,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE webhook_deliveries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
endpoint_id UUID NOT NULL REFERENCES webhook_endpoints(id),
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
status TEXT NOT NULL DEFAULT 'pending', -- pending, success, failed
http_status INTEGER,
response_body TEXT,
attempt_count INTEGER NOT NULL DEFAULT 0,
next_retry_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Delivery Flow
- On domain event, look up active webhook endpoints for that team and event type
- Queue a delivery job per endpoint (do not deliver synchronously)
- In the worker: serialize payload as JSON, generate timestamp, create HMAC-SHA256 signature of
{timestamp}.{payload}using the endpoint's secret - POST to the endpoint URL with headers:
X-Webhook-Id,X-Webhook-Timestamp,X-Webhook-Signature(v1={signature}) - Record delivery status (success/failed, HTTP status, response body)
- On failure, let the queue retry with exponential backoff: 5s, 30s, 2m, 15m, 1h, 4h, 12h, 24h (8 attempts total)
Receiving Webhooks (External Systems to Your Product)
When receiving webhooks from external services (e.g., Stripe, GitHub):
- Verify the signature before any processing (use the provider's SDK or verify HMAC manually)
- Return 200 quickly (< 5 seconds) -- queue processing for async execution if needed
- Deduplicate by storing processed event IDs -- check before processing, record after
- Route by event type to the appropriate service handler
- Dead letter queue for events that fail processing after retries
- Log everything: event type, processing result, errors
Idempotency
Why It Matters
External APIs fail, networks timeout, webhooks retry. Every integration operation must be safe to execute more than once.
Idempotency Key Pattern
// For outgoing API calls
async function createExternalResource(data: ResourceInput, idempotencyKey: string) {
// Check if we already processed this operation
const existing = await prisma.idempotencyRecord.findUnique({
where: { key: idempotencyKey },
});
if (existing) return existing.result;
// Execute the operation
const result = await externalApi.create(data, {
headers: { "Idempotency-Key": idempotencyKey },
});
// Record the result
await prisma.idempotencyRecord.create({
data: { key: idempotencyKey, result, expiresAt: addDays(new Date(), 7) },
});
return result;
}
// Usage
const key = `create-stripe-customer-${userId}`;
const customer = await createExternalResource(customerData, key);
Idempotency Rules
- Generate keys deterministically: Based on the operation and its inputs, not randomly
- Store results: Return the stored result on duplicate requests
- Set TTL: Clean up old idempotency records after 7-30 days
- Scope appropriately: Per user, per team, or per resource
Rate Limit Handling
Respecting External Rate Limits
// Rate-limited API client
class RateLimitedClient {
private queue: PQueue;
constructor(maxConcurrent: number, intervalMs: number) {
this.queue = new PQueue({
concurrency: maxConcurrent,
interval: intervalMs,
intervalCap: maxConcurrent,
});
}
async request<T>(fn: () => Promise<T>): Promise<T> {
return this.queue.add(async () => {
try {
return await fn();
} catch (error) {
if (error.status === 429) {
const retryAfter = parseInt(error.headers["retry-after"] ?? "60", 10);
await sleep(retryAfter * 1000);
return fn();
}
throw error;
}
});
}
}
// Usage
const slackClient = new RateLimitedClient(1, 1000); // 1 req/sec
await slackClient.request(() => slack.chat.postMessage({ channel, text }));
Rate Limit Best Practices
- Read the docs: Know the limits before you hit them
- Use exponential backoff on 429 responses, with jitter
- Respect Retry-After headers exactly
- Cache aggressively: Reduce calls to external APIs by caching responses
- Batch requests: Use bulk endpoints where available
- Monitor usage: Track API call counts to detect limit approaches
Integration Marketplace
Marketplace Architecture
Your Product
├── Native Integrations (built by you)
│ ├── Slack notification
│ ├── Stripe billing
│ └── GitHub sync
├── Partner Integrations (built with partners)
│ └── Installed via OAuth + configuration
└── Community Integrations (built by developers)
└── Published via developer portal + review process
Integration Configuration Model
CREATE TABLE integration_definitions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT NOT NULL UNIQUE, -- 'slack', 'github', 'custom-crm'
name TEXT NOT NULL,
description TEXT NOT NULL,
category TEXT NOT NULL, -- 'communication', 'developer-tools', 'analytics'
provider TEXT NOT NULL DEFAULT 'native', -- 'native', 'partner', 'community'
auth_type TEXT NOT NULL, -- 'oauth2', 'api_key', 'webhook'
config_schema JSONB NOT NULL DEFAULT '{}', -- JSON Schema for user configuration
logo_url TEXT,
is_published BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE installed_integrations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
definition_id UUID NOT NULL REFERENCES integration_definitions(id),
config JSONB NOT NULL DEFAULT '{}', -- User-specific configuration
credentials JSONB NOT NULL DEFAULT '{}', -- Encrypted tokens/keys
is_active BOOLEAN NOT NULL DEFAULT true,
installed_by UUID NOT NULL REFERENCES users(id),
last_synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (team_id, definition_id)
);
Integration Lifecycle
1. Discovery: User browses marketplace, finds integration
2. Install: User clicks install, completes OAuth flow or enters API key
3. Configure: User sets preferences (which channel, which events, mapping fields)
4. Activate: Integration starts processing events
5. Monitor: User sees sync status, error logs, last activity
6. Manage: User can pause, reconfigure, or disconnect
Zapier / Make Integration
Building a Zapier Integration
Your product needs to expose:
-
Triggers (events that start a Zap):
New Project Created → webhook: project.created New Team Member Added → webhook: member.joined Invoice Paid → webhook: invoice.paid -
Actions (things Zapier can do in your product):
Create Project → POST /api/v1/projects Update Project Status → PATCH /api/v1/projects/{id} Send Notification → POST /api/v1/notifications -
Searches (lookups):
Find Project by Name → GET /api/v1/projects?q={name} Find User by Email → GET /api/v1/users?email={email}
Zapier Integration Requirements
- REST hooks for triggers (subscribe/unsubscribe endpoints, not polling)
- Authentication: OAuth 2.0 or API key
- Pagination: Cursor-based for list endpoints
- Sample data: Return example payloads for each trigger/action
- Error messages: Clear, actionable error messages that Zapier users can understand
Embeddable Widgets and SDKs
JavaScript SDK
Build a typed SDK class that wraps fetch with your API base URL, handles auth via API key in the Authorization header, and exposes resource-specific methods (e.g., sdk.projects.list(), sdk.projects.create()). Publish to npm for developer consumption.
Embeddable Widget
Use an iframe-based embed with a <script> tag. The script reads a data-key attribute for the publishable API key, creates an iframe pointing to your widget URL, and uses postMessage for parent-child communication. Always validate event.origin on incoming messages.
Event-Driven Architecture
Use an internal event bus to decouple core business logic from integration side effects. When a domain event fires (e.g., project.created), registered handlers dispatch webhooks, send Slack notifications, and track analytics -- all without the core service knowing about integrations.
Design events with: unique id, type (resource.action), teamId, actorId, data payload, and metadata (timestamp, source, correlationId for tracing).
Integration Testing
| Approach | When to Use | Tools |
|---|---|---|
| Mock HTTP | Unit tests, CI pipeline | nock, msw, wiremock |
| Sandbox environments | Integration tests | Stripe test mode, Slack sandbox |
| Record/replay | Regression tests | Polly.js, VCR |
| Contract tests | API compatibility | Pact |
Use MSW (Mock Service Worker) in tests to intercept HTTP calls to external APIs and return controlled responses, keeping tests fast and deterministic.
Error Handling for Integrations
Graceful degradation: Integration failures must never break core product functionality. Wrap all external calls in try/catch, log warnings, and queue for retry. Never let a Slack notification failure prevent a project from being created.
Circuit breaker: Track consecutive failures per external service. After a threshold (e.g., 5 failures), stop calling the service for a cooldown period (e.g., 60 seconds). This prevents cascading failures and reduces load on struggling external services. After the cooldown, allow one test request (half-open state) and reset if it succeeds.
Output Format
When providing integration guidance, deliver:
- Integration architecture: Pattern selection (direct, webhook, queue) with rationale
- Authentication flow: How users connect the integration (OAuth, API key, webhook)
- Data flow diagram: How data moves between systems, including error paths
- Data model: Tables for integration configurations, credentials, delivery logs
- Reliability strategy: Retry logic, idempotency, circuit breakers, monitoring
- Code examples: Working implementation for key flows (not pseudocode)
- Testing approach: How to test integrations without hitting live APIs
Anti-Patterns to Avoid
- Synchronous external calls in request path: External API calls should not block user-facing requests. Use queues.
- No retry logic: All external calls fail sometimes. Build retries into every integration.
- No idempotency: Without idempotency keys, retries create duplicates
- Storing credentials in plain text: Encrypt OAuth tokens and API keys at rest
- No circuit breaker: One failing integration should not bring down your entire product
- Tight coupling: Integration logic mixed into core business logic. Keep integrations in separate modules.
- No webhook signature verification: Always verify incoming webhook signatures before processing
- Ignoring rate limits: Hammering external APIs gets your integration blocked
Related Skills
- apis: Design the API contracts that integrations consume and expose
- backend: Implement the server-side infrastructure for integration processing
- authentication: Implement OAuth flows for user-authorized integrations
- security: Secure credential storage, webhook verification, and data handling