> ## Documentation Index
> Fetch the complete documentation index at: https://docs.play2sell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Integrations

> Build custom integrations with SalesOS using the REST API, webhooks, and best practices.

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

1. **Set up webhooks** for the events you care about (e.g., `deal.won`, `lead.created`)
2. **Build a receiver** that processes webhook payloads
3. **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:

1. **Listen for events** in your external system (or poll on a schedule)
2. **Transform the data** to match SalesOS field formats
3. **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:

1. **Webhook from SalesOS** updates the external system
2. **Webhook or poll from external** updates SalesOS
3. **Conflict resolution** handles simultaneous changes

<Warning>
  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).
</Warning>

### Scheduled Batch Sync

For systems that do not support real-time events:

1. **Run on a schedule** (e.g., every hour via cron)
2. **Fetch changes since last sync** using `updated_after` filters
3. **Process changes** in batches
4. **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:

```bash theme={null}
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](/api/authentication).

### Step 3: Implement the Integration

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={null}
    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;
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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()
    ```
  </Tab>
</Tabs>

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

```javascript theme={null}
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);
```

<Tip>
  Always respond to webhooks quickly (within 10 seconds) and process the payload asynchronously. This prevents timeouts and retries.
</Tip>

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

<CardGroup cols={2}>
  <Card title="API Conventions" icon="code" href="/api/conventions">
    Review API conventions
  </Card>

  <Card title="Webhooks" icon="webhook" href="/api/integrations/webhooks">
    Set up webhook endpoints
  </Card>
</CardGroup>
