| 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
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)
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)
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
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
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