home / skills / aj-geddes / useful-ai-prompts / third-party-integration

third-party-integration skill

/skills/third-party-integration

This skill helps you integrate third-party services with robust error handling, retries, and data transformation across payments, messaging, and analytics.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill third-party-integration

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
12.6 KB
---
name: third-party-integration
description: Integrate external APIs and services with error handling, retry logic, and data transformation. Use when connecting to payment processors, messaging services, analytics platforms, or other third-party providers.
---

# Third-Party Integration

## Overview

Build robust integrations with external services using standardized patterns for API calls, error handling, authentication, and data transformation.

## When to Use

- Integrating payment processors (Stripe, PayPal)
- Using messaging services (SendGrid, Twilio)
- Connecting to analytics platforms (Mixpanel, Segment)
- Syncing with storage services (AWS S3, Google Cloud)
- Integrating CRM systems (Salesforce, HubSpot)
- Building multi-service architectures

## Instructions

### 1. **Third-Party Client Wrapper**

```javascript
const axios = require('axios');

class ThirdPartyClient {
  constructor(config) {
    this.apiKey = config.apiKey;
    this.baseUrl = config.baseUrl;
    this.timeout = config.timeout || 30000;
    this.retryAttempts = config.retryAttempts || 3;
    this.retryDelay = config.retryDelay || 1000;
    this.client = axios.create({
      baseURL: this.baseUrl,
      timeout: this.timeout,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      }
    });
  }

  async request(method, endpoint, data = null, options = {}) {
    let lastError;

    for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
      try {
        const response = await this.client({
          method,
          url: endpoint,
          data,
          timeout: this.timeout,
          ...options
        });

        return {
          success: true,
          data: response.data,
          statusCode: response.status,
          headers: response.headers
        };
      } catch (error) {
        lastError = error;

        // Check if error is retryable
        if (!this.isRetryable(error) || attempt === this.retryAttempts - 1) {
          break;
        }

        // Exponential backoff
        const delay = this.retryDelay * Math.pow(2, attempt);
        await this.sleep(delay);
      }
    }

    return this.handleError(lastError);
  }

  isRetryable(error) {
    if (!error.response) return true; // Network error

    const status = error.response.status;
    // Retry on 5xx and specific 4xx errors
    return status >= 500 || [408, 429].includes(status);
  }

  handleError(error) {
    if (error.response) {
      return {
        success: false,
        error: {
          message: error.response.data?.message || error.message,
          code: error.response.data?.code || error.response.status,
          status: error.response.status,
          data: error.response.data
        }
      };
    }

    return {
      success: false,
      error: {
        message: error.message,
        code: 'NETWORK_ERROR'
      }
    };
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  async get(endpoint) {
    return this.request('GET', endpoint);
  }

  async post(endpoint, data) {
    return this.request('POST', endpoint, data);
  }

  async put(endpoint, data) {
    return this.request('PUT', endpoint, data);
  }

  async delete(endpoint) {
    return this.request('DELETE', endpoint);
  }
}

// Usage
const stripeClient = new ThirdPartyClient({
  apiKey: process.env.STRIPE_API_KEY,
  baseUrl: 'https://api.stripe.com/v1',
  timeout: 30000,
  retryAttempts: 3
});

const result = await stripeClient.post('/charges', {
  amount: 10000,
  currency: 'usd',
  source: 'tok_visa'
});
```

### 2. **Payment Processor Integration (Stripe)**

```javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

class PaymentService {
  async createCharge(userId, amount, paymentMethodId) {
    try {
      const customer = await this.getOrCreateCustomer(userId);

      const charge = await stripe.charges.create({
        amount: Math.round(amount * 100), // cents
        currency: 'usd',
        customer: customer.id,
        payment_method: paymentMethodId,
        confirm: true
      });

      // Log transaction
      await Transaction.create({
        userId,
        chargeId: charge.id,
        amount,
        status: charge.status,
        createdAt: new Date(charge.created * 1000)
      });

      return {
        success: true,
        chargeId: charge.id,
        status: charge.status
      };
    } catch (error) {
      console.error('Charge error:', error.message);

      if (error.code === 'card_declined') {
        return { success: false, error: 'Card declined' };
      }

      throw error;
    }
  }

  async refund(chargeId, amount = null) {
    try {
      const refund = await stripe.refunds.create({
        charge: chargeId,
        amount: amount ? Math.round(amount * 100) : undefined
      });

      await Transaction.updateOne(
        { chargeId },
        { refundId: refund.id, status: 'refunded' }
      );

      return { success: true, refundId: refund.id };
    } catch (error) {
      console.error('Refund error:', error.message);
      throw error;
    }
  }

  async getOrCreateCustomer(userId) {
    let customer = await Customer.findOne({ userId });

    if (!customer) {
      const stripeCustomer = await stripe.customers.create({
        metadata: { userId }
      });

      customer = await Customer.create({
        userId,
        stripeId: stripeCustomer.id
      });
    }

    return customer;
  }

  async handleWebhook(event) {
    switch (event.type) {
      case 'charge.succeeded':
        await this.handleChargeSucceeded(event.data.object);
        break;
      case 'charge.failed':
        await this.handleChargeFailed(event.data.object);
        break;
      case 'refund.created':
        await this.handleRefund(event.data.object);
        break;
    }
  }
}

// Webhook endpoint
app.post('/webhooks/stripe', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];

  try {
    const event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );

    await paymentService.handleWebhook(event);
    res.json({ received: true });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});
```

### 3. **Email Service Integration (SendGrid)**

```javascript
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);

class EmailService {
  async sendEmail(to, templateId, templateData = {}) {
    try {
      const message = {
        to,
        from: process.env.FROM_EMAIL,
        templateId,
        dynamicTemplateData: {
          ...templateData,
          timestamp: new Date().toISOString()
        },
        trackingSettings: {
          clickTracking: { enabled: true },
          openTracking: { enabled: true }
        }
      };

      const response = await sgMail.send(message);

      // Log email
      await EmailLog.create({
        to,
        templateId,
        messageId: response[0].headers['x-message-id'],
        status: 'sent',
        sentAt: new Date()
      });

      return { success: true, messageId: response[0].headers['x-message-id'] };
    } catch (error) {
      console.error('Email error:', error.message);

      await EmailLog.create({
        to,
        templateId,
        error: error.message,
        status: 'failed'
      });

      throw error;
    }
  }

  async sendBulk(recipients, templateId, templateData) {
    const promises = recipients.map(recipient =>
      this.sendEmail(recipient, templateId, templateData).catch(err => ({
        recipient,
        error: err.message
      }))
    );

    return Promise.allSettled(promises);
  }

  async handleWebhook(event) {
    const { messageId, event: eventType } = event;

    await EmailLog.updateOne(
      { messageId },
      { status: eventType, updatedAt: new Date() }
    );
  }
}

// Usage
const emailService = new EmailService();

app.post('/api/send-welcome-email', async (req, res) => {
  const { email, firstName } = req.body;

  const result = await emailService.sendEmail(email, 'd-welcome-template-id', {
    firstName
  });

  res.json(result);
});
```

### 4. **Python Third-Party Integration**

```python
import requests
import time
from typing import Optional, Dict, Any
from datetime import datetime
import logging

logger = logging.getLogger(__name__)

class APIClient:
    def __init__(self, api_key: str, base_url: str, timeout: int = 30):
        self.api_key = api_key
        self.base_url = base_url
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        })

    def request(
        self,
        method: str,
        endpoint: str,
        data: Optional[Dict] = None,
        max_retries: int = 3
    ) -> Dict[str, Any]:
        url = f"{self.base_url}/{endpoint}"
        attempt = 0

        while attempt < max_retries:
            try:
                response = self.session.request(
                    method,
                    url,
                    json=data,
                    timeout=self.timeout
                )

                if response.status_code >= 200 and response.status_code < 300:
                    return {
                        'success': True,
                        'data': response.json(),
                        'status': response.status_code
                    }

                if response.status_code >= 500 or response.status_code == 429:
                    raise requests.RequestException(f"HTTP {response.status_code}")

                return {
                    'success': False,
                    'error': response.json().get('message', 'Error'),
                    'status': response.status_code
                }

            except requests.RequestException as e:
                attempt += 1
                if attempt >= max_retries:
                    logger.error(f"API request failed: {e}")
                    return {
                        'success': False,
                        'error': str(e),
                        'status': None
                    }

                wait_time = 2 ** attempt
                time.sleep(wait_time)

        return {'success': False, 'error': 'Max retries exceeded'}

    def get(self, endpoint: str) -> Dict[str, Any]:
        return self.request('GET', endpoint)

    def post(self, endpoint: str, data: Dict) -> Dict[str, Any]:
        return self.request('POST', endpoint, data)

    def put(self, endpoint: str, data: Dict) -> Dict[str, Any]:
        return self.request('PUT', endpoint, data)

    def delete(self, endpoint: str) -> Dict[str, Any]:
        return self.request('DELETE', endpoint)

# Payment processor example
class PaymentGateway(APIClient):
    def create_payment(self, amount: float, currency: str, customer_id: str):
        return self.post('charges', {
            'amount': int(amount * 100),
            'currency': currency,
            'customer': customer_id
        })

    def refund(self, charge_id: str, amount: Optional[float] = None):
        return self.post(f'charges/{charge_id}/refund', {
            'amount': int(amount * 100) if amount else None
        })
```

### 5. **Data Transformation**

```javascript
class DataMapper {
  static stripeChargeToTransaction(charge) {
    return {
      id: charge.id,
      amount: charge.amount / 100,
      currency: charge.currency,
      status: charge.status,
      customerId: charge.customer,
      createdAt: new Date(charge.created * 1000),
      metadata: charge.metadata
    };
  }

  static sendgridEmailToLog(event) {
    return {
      messageId: event.sg_message_id,
      email: event.email,
      eventType: event.event,
      timestamp: new Date(event.timestamp * 1000),
      metadata: event
    };
  }

  static awsS3FileToRecord(s3Object) {
    return {
      key: s3Object.Key,
      size: s3Object.Size,
      lastModified: s3Object.LastModified,
      etag: s3Object.ETag,
      bucket: s3Object.Bucket
    };
  }
}
```

## Best Practices

### ✅ DO
- Implement retry logic with exponential backoff
- Validate webhook signatures
- Log all API interactions
- Use environment variables for secrets
- Transform API responses to internal models
- Implement circuit breakers for critical services
- Monitor API quota and rate limits
- Add proper error handling
- Use timeouts appropriately
- Test with sandbox/test API keys

### ❌ DON'T
- Hardcode API keys
- Retry all errors indefinitely
- Log sensitive data
- Trust unvalidated webhook data
- Ignore rate limits
- Make synchronous blocking calls
- Expose vendor-specific details to clients
- Skip error handling
- Use production keys in tests

Overview

This skill provides a ready-to-use pattern for integrating external APIs and services with robust error handling, retries, authentication, and data transformation. It packages client wrappers, webhook handling, payment/email examples, and data mappers to accelerate reliable third-party connections. Use it to standardize calls, logging, and failure modes across payment processors, messaging providers, analytics, and storage services.

How this skill works

The skill supplies generic client wrappers that centralize HTTP requests, headers, timeouts, retry attempts, and exponential backoff. It normalizes responses and errors into consistent success/error objects, validates and processes webhooks, and offers example service implementations for payments (Stripe) and email (SendGrid). Data mappers convert vendor payloads into internal models so the rest of the application can work with predictable shapes.

When to use it

  • Connecting payment processors (Stripe, PayPal) in production or staging
  • Sending transactional or bulk emails via SendGrid or similar providers
  • Integrating SMS or messaging services (Twilio) with retries and logs
  • Syncing files or objects with cloud storage (S3, GCS)
  • Forwarding analytics or CRM events to external platforms
  • Building resilient multi-service architectures that depend on external APIs

Best practices

  • Implement exponential backoff with a capped retry count and jitter
  • Validate webhook signatures and fail fast on invalid events
  • Log requests, responses, and errors but never log secrets or PII
  • Use environment variables for keys and separate test/prod credentials
  • Transform vendor payloads into internal models before business use
  • Monitor rate limits and add circuit breakers for critical dependencies

Example use cases

  • Charge a customer and record a canonical transaction after a successful Stripe charge
  • Send templated welcome emails and log message IDs with SendGrid webhooks
  • Retry transient network failures when calling an external analytics API
  • Map S3 object events into file records for downstream processing
  • Handle refund flows and update local transaction state reliably
  • Bulk-send emails while collecting per-recipient delivery errors for retry

FAQ

How do I decide retry counts and delays?

Start with a small number (2–5 attempts) and exponential backoff with jitter; increase only after observing real failure patterns and respecting provider rate limits.

What should I transform in vendor responses?

Normalize currency/amount units, timestamps, IDs, and any nested metadata so your application uses consistent types and field names.