Building Custom Integrations
When the pre-built integrations do not cover your needs, build a custom integration using the SalesOS REST API and webhooks. This guide covers architecture patterns, best practices, and common scenarios.
Integration Patterns
One-Way Sync (SalesOS to External)
Push data from SalesOS to another system:
- Set up webhooks for the events you care about (e.g.,
deal.won, lead.created)
- Build a receiver that processes webhook payloads
- Transform and push the data to your target system
Best for: Sending deal data to your ERP, syncing leads to marketing tools, posting notifications to chat systems.
One-Way Sync (External to SalesOS)
Pull or push data from an external system into SalesOS:
- Listen for events in your external system (or poll on a schedule)
- Transform the data to match SalesOS field formats
- Call the SalesOS API to create or update records
Best for: Importing leads from web forms, syncing contacts from an external CRM, creating deals from e-commerce orders.
Two-Way Sync
Keep data synchronized between SalesOS and another system:
- Webhook from SalesOS updates the external system
- Webhook or poll from external updates SalesOS
- Conflict resolution handles simultaneous changes
Two-way syncs can create infinite loops if not carefully designed. Always include a mechanism to detect and skip updates that originated from the other system (e.g., using a sync marker field or comparing timestamps).
Scheduled Batch Sync
For systems that do not support real-time events:
- Run on a schedule (e.g., every hour via cron or n8n)
- Fetch changes since last sync using
updated_after filters
- Process changes in batches
- Track sync state (last sync timestamp, page cursors)
Building Your Integration
Step 1: Plan Your Data Flow
Before writing code, document:
- Which SalesOS entities you need (leads, deals, users)
- Which fields map between systems
- Which direction data flows (one-way or two-way)
- How often data needs to sync (real-time, hourly, daily)
- How to handle conflicts and errors
Step 2: Set Up Authentication
Create an API client with the minimum required scopes:
curl -X POST "https://auth.play2sell.com/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"audience": "https://api.play2sell.com",
"grant_type": "client_credentials"
}'
Store the token and handle refresh logic. See the Authentication guide.
Step 3: Implement the Integration
const axios = require('axios');
const salesos = axios.create({
baseURL: 'https://api.play2sell.com/v1',
headers: {
'Authorization': `Bearer ${process.env.SALESOS_TOKEN}`,
'Content-Type': 'application/json'
}
});
// Create a lead from an external source
async function syncLeadToSalesOS(externalLead) {
try {
const response = await salesos.post('/leads', {
name: externalLead.fullName,
email: externalLead.email,
phone: externalLead.phone,
company: externalLead.company,
source: 'external_crm',
custom_fields: {
external_id: externalLead.id
}
});
return response.data;
} catch (error) {
if (error.response?.status === 409) {
console.log('Lead already exists, updating...');
// Handle duplicate
}
throw error;
}
}
import requests
import os
class SalesOSClient:
def __init__(self):
self.base_url = 'https://api.play2sell.com/v1'
self.token = os.environ['SALESOS_TOKEN']
self.headers = {
'Authorization': f'Bearer {self.token}',
'Content-Type': 'application/json'
}
def create_lead(self, external_lead):
payload = {
'name': external_lead['full_name'],
'email': external_lead['email'],
'phone': external_lead.get('phone'),
'company': external_lead.get('company'),
'source': 'external_crm',
'custom_fields': {
'external_id': external_lead['id']
}
}
response = requests.post(
f'{self.base_url}/leads',
json=payload,
headers=self.headers
)
response.raise_for_status()
return response.json()
Step 4: Handle Errors
Your integration should handle:
- Rate limiting — Respect
429 responses and back off using the Retry-After header
- Validation errors — Log
422 responses and fix data mapping issues
- Network failures — Implement retry logic with exponential backoff
- Duplicate detection — Handle
409 Conflict responses gracefully
Step 5: Monitor and Maintain
- Log all API calls and responses for debugging
- Set up alerts for integration failures
- Monitor API usage against rate limits
- Keep your token refresh mechanism working
- Test after SalesOS updates
Webhook Receiver Example
A minimal webhook receiver in Node.js (Express):
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/salesos', express.json(), (req, res) => {
// Verify signature
const signature = req.headers['x-salesos-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// Process the event
const { type, data } = req.body;
console.log(`Received event: ${type}`, data);
// Respond quickly, process async
res.status(200).send('OK');
// Handle the event asynchronously
processEvent(type, data).catch(console.error);
});
app.listen(3000);
Always respond to webhooks quickly (within 10 seconds) and process the payload asynchronously. This prevents timeouts and retries.
Best Practices
- Use idempotent operations — Your integration should produce the same result if the same event is processed twice
- Map external IDs — Store the external system’s ID in a SalesOS custom field to enable lookups and deduplication
- Log everything — Comprehensive logging makes debugging much easier
- Test with staging — Use the SalesOS staging environment to test before going to production
- Handle partial failures — In batch operations, do not let one failure stop the entire batch
Next Steps