agentsclimarketplace

Integrations

Skill lucaspedrozaem/saasskills/skills/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

Install
npx -y skills add lucaspedrozaem/saasskills --skill integrations

Assembled 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:

  1. What are you integrating with? Specific third-party services or general integration architecture
  2. Direction? Your product consuming external APIs, exposing APIs for others, or both
  3. Integration type? One-off connection, user-configured integration, or full marketplace
  4. Authentication? How the third-party authenticates (API key, OAuth 2.0, webhook secret)
  5. Data flow? Real-time sync, periodic polling, event-driven webhooks, or batch import/export
  6. Reliability requirements? Can you tolerate delays? What happens if the external service is down?
  7. Scale? Number of connected accounts, event volume, data transfer size
  8. 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

PatternHow It WorksBest ForComplexity
Direct API callYour server calls their API synchronouslySimple reads, on-demand actionsLow
Webhook-basedEvents push data between systemsReal-time notifications, event-driven syncMedium
Queue-basedEvents go through a message queueHigh volume, reliability-criticalMedium-high
PollingPeriodically fetch data from external APIAPIs without webhooks, data syncLow-medium
iPaaS (Zapier/Make)Users connect via no-code platformQuick integration coverage, long tailLow (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:action format
  • 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

  1. On domain event, look up active webhook endpoints for that team and event type
  2. Queue a delivery job per endpoint (do not deliver synchronously)
  3. In the worker: serialize payload as JSON, generate timestamp, create HMAC-SHA256 signature of {timestamp}.{payload} using the endpoint's secret
  4. POST to the endpoint URL with headers: X-Webhook-Id, X-Webhook-Timestamp, X-Webhook-Signature (v1={signature})
  5. Record delivery status (success/failed, HTTP status, response body)
  6. 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):

  1. Verify the signature before any processing (use the provider's SDK or verify HMAC manually)
  2. Return 200 quickly (< 5 seconds) -- queue processing for async execution if needed
  3. Deduplicate by storing processed event IDs -- check before processing, record after
  4. Route by event type to the appropriate service handler
  5. Dead letter queue for events that fail processing after retries
  6. 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:

  1. Triggers (events that start a Zap):

    New Project Created    → webhook: project.created
    New Team Member Added  → webhook: member.joined
    Invoice Paid           → webhook: invoice.paid
    
  2. 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
    
  3. 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

ApproachWhen to UseTools
Mock HTTPUnit tests, CI pipelinenock, msw, wiremock
Sandbox environmentsIntegration testsStripe test mode, Slack sandbox
Record/replayRegression testsPolly.js, VCR
Contract testsAPI compatibilityPact

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:

  1. Integration architecture: Pattern selection (direct, webhook, queue) with rationale
  2. Authentication flow: How users connect the integration (OAuth, API key, webhook)
  3. Data flow diagram: How data moves between systems, including error paths
  4. Data model: Tables for integration configurations, credentials, delivery logs
  5. Reliability strategy: Retry logic, idempotency, circuit breakers, monitoring
  6. Code examples: Working implementation for key flows (not pseudocode)
  7. 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

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.