Integration Examples
Complete end-to-end examples showing how to integrate with the ISAO API for common use cases.
📋 Table of Contents
- Complete Node.js Integration
- Python CRM Sync
- PHP E-commerce Integration
- Zapier Custom App
- Make.com Scenarios
- Lead Management Workflow
- Sales Pipeline Automation
🚀 Complete Node.js Integration
This example shows a complete Node.js application that syncs data between ISAO and an external system.
Setup
npm init -y
npm install axios dotenv express
Environment Variables
# .env
ISAO_API_KEY=ck_your_api_key_here
ISAO_BASE_URL=https://staging.isao.io/api/external
WEBHOOK_SECRET=your_webhook_secret
PORT=3000
Main Application
// app.js
require('dotenv').config();
const express = require('express');
const axios = require('axios');
class IsaoIntegration {
constructor() {
this.apiKey = process.env.ISAO_API_KEY;
this.baseUrl = process.env.ISAO_BASE_URL;
this.client = axios.create({
baseURL: this.baseUrl,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
// Setup response interceptor for error handling
this.client.interceptors.response.use(
(response) => response,
(error) => this.handleApiError(error)
);
}
async handleApiError(error) {
if (error.response) {
console.error('API Error:', {
status: error.response.status,
data: error.response.data,
url: error.config.url
});
// Handle rate limiting
if (error.response.status === 429) {
const retryAfter = error.response.headers['retry-after'] || 60;
console.log(`Rate limited, waiting ${retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return this.client.request(error.config);
}
}
throw error;
}
// Test API connection
async testConnection() {
try {
const response = await this.client.get('/test');
console.log('✅ API connection successful:', response.data);
return true;
} catch (error) {
console.error('❌ API connection failed');
return false;
}
}
// Create or update contact
async upsertContact(contactData) {
try {
// Try to create contact first
const response = await this.client.post('/contacts', contactData);
console.log('✅ Contact created:', response.data.data.id);
return response.data.data;
} catch (error) {
if (error.response?.status === 409) {
// Contact exists, try to find and update
const existingId = error.response.data.existingContactId;
if (existingId) {
const updated = await this.client.put(`/contacts/${existingId}`, contactData);
console.log('✅ Contact updated:', existingId);
return updated.data.data;
}
}
throw error;
}
}
// Sync contacts from external source
async syncContactsFromCRM(crmContacts) {
console.log(`📞 Syncing ${crmContacts.length} contacts...`);
const results = { created: 0, updated: 0, errors: 0 };
for (const crmContact of crmContacts) {
try {
const isaoContact = this.transformCRMToIsao(crmContact);
await this.upsertContact(isaoContact);
results.created++;
} catch (error) {
console.error('❌ Failed to sync contact:', crmContact.id, error.message);
results.errors++;
}
}
console.log('📊 Sync results:', results);
return results;
}
// Transform CRM contact format to ISAO format
transformCRMToIsao(crmContact) {
return {
name: `${crmContact.first_name} ${crmContact.last_name}`.trim(),
firstName: crmContact.first_name,
lastName: crmContact.last_name,
phoneNumber: crmContact.phone,
email: crmContact.email,
position: crmContact.job_title,
currentCompany: crmContact.company,
city: crmContact.city,
country: crmContact.country,
tags: crmContact.tags || []
};
}
// Create deal from external lead
async createDealFromLead(leadData) {
try {
// First, create or find contact
const contact = await this.upsertContact({
name: leadData.contactName,
phoneNumber: leadData.contactPhone,
email: leadData.contactEmail,
currentCompany: leadData.company
});
// Create deal
const dealData = {
title: leadData.opportunityTitle,
description: leadData.description,
value: leadData.estimatedValue,
currency: leadData.currency || 'EUR',
stage: 'qualification',
priority: leadData.priority || 'medium',
probability: leadData.probability || 50,
expectedCloseDate: leadData.expectedCloseDate,
source: leadData.source || 'api',
contactId: contact.id
};
const response = await this.client.post('/deals', dealData);
console.log('✅ Deal created:', response.data.data.id);
return response.data.data;
} catch (error) {
console.error('❌ Failed to create deal:', error.message);
throw error;
}
}
// Update deal stage based on external pipeline
async updateDealStage(dealId, newStage, status = null) {
try {
const updateData = { stage: newStage };
if (status) {
updateData.status = status;
if (status !== 'open') {
updateData.actualCloseDate = new Date().toISOString();
}
}
const response = await this.client.put(`/deals/${dealId}/status`, updateData);
console.log('✅ Deal stage updated:', dealId, newStage);
return response.data.data;
} catch (error) {
console.error('❌ Failed to update deal stage:', error.message);
throw error;
}
}
// Setup webhook subscription
async setupWebhook(webhookUrl) {
try {
const events = ['contact.created', 'contact.updated', 'deal.created', 'deal.won', 'deal.lost'];
for (const event of events) {
await this.client.post('/webhooks/subscribe', {
event,
target_url: webhookUrl
});
console.log(`✅ Webhook subscribed: ${event}`);
}
} catch (error) {
console.error('❌ Failed to setup webhooks:', error.message);
throw error;
}
}
}
// Express app for webhook handling
const app = express();
app.use(express.json());
// Webhook endpoint
app.post('/webhook/isao', async (req, res) => {
const event = req.body;
console.log('📡 Webhook received:', event.event);
try {
switch (event.event) {
case 'contact.created':
await handleContactCreated(event.data);
break;
case 'deal.won':
await handleDealWon(event.data);
break;
case 'deal.lost':
await handleDealLost(event.data);
break;
default:
console.log('ℹ️ Unhandled event type:', event.event);
}
res.json({ success: true });
} catch (error) {
console.error('❌ Webhook processing error:', error);
res.status(500).json({ error: 'Webhook processing failed' });
}
});
async function handleContactCreated(contact) {
console.log('👤 New contact created:', contact.name);
// Sync to external CRM
await syncContactToExternalCRM(contact);
}
async function handleDealWon(deal) {
console.log('💰 Deal won:', deal.title, deal.value, deal.currency);
// Generate invoice, send congratulations email, etc.
await generateInvoice(deal);
await sendCongratulationsEmail(deal);
}
async function handleDealLost(deal) {
console.log('😞 Deal lost:', deal.title);
// Add to nurturing campaign, send feedback survey, etc.
await addToNurturingCampaign(deal);
}
// Mock external functions
async function syncContactToExternalCRM(contact) {
console.log('📤 Syncing contact to external CRM...');
// Implementation depends on your CRM's API
}
async function generateInvoice(deal) {
console.log('📄 Generating invoice for won deal...');
// Implementation depends on your billing system
}
async function sendCongratulationsEmail(deal) {
console.log('📧 Sending congratulations email...');
// Implementation depends on your email service
}
async function addToNurturingCampaign(deal) {
console.log('📮 Adding lost deal to nurturing campaign...');
// Implementation depends on your marketing automation
}
// Main execution
async function main() {
const isao = new IsaoIntegration();
// Test connection
const connected = await isao.testConnection();
if (!connected) {
console.error('Failed to connect to ISAO API');
process.exit(1);
}
// Setup webhook endpoint
const webhookUrl = `http://localhost:${process.env.PORT}/webhook/isao`;
await isao.setupWebhook(webhookUrl);
// Example: Sync contacts from external CRM
const exampleCRMContacts = [
{
id: 'crm_123',
first_name: 'Alice',
last_name: 'Johnson',
email: 'alice@example.com',
phone: '+33123456789',
company: 'Tech Innovations',
job_title: 'CTO',
city: 'Paris',
country: 'France',
tags: ['technical', 'decision-maker']
}
];
await isao.syncContactsFromCRM(exampleCRMContacts);
// Example: Create deal from lead
const leadData = {
contactName: 'Bob Wilson',
contactPhone: '+33987654321',
contactEmail: 'bob@example.com',
company: 'Wilson Corp',
opportunityTitle: 'Enterprise Software License',
description: 'Annual license for 100 users',
estimatedValue: 50000,
currency: 'EUR',
priority: 'high',
probability: 75,
expectedCloseDate: '2024-12-31',
source: 'website'
};
const deal = await isao.createDealFromLead(leadData);
// Example: Update deal stage
setTimeout(async () => {
await isao.updateDealStage(deal.id, 'proposal');
}, 5000);
console.log('🚀 Integration started successfully');
}
// Start the application
if (require.main === module) {
app.listen(process.env.PORT, () => {
console.log(`🌐 Webhook server listening on port ${process.env.PORT}`);
main().catch(console.error);
});
}
module.exports = { IsaoIntegration };
🐍 Python CRM Sync
Complete Python application for syncing data with Salesforce or HubSpot.
import os
import requests
import json
import time
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime, timedelta
import logging
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class Contact:
name: str
phone_number: str
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
position: Optional[str] = None
company: Optional[str] = None
city: Optional[str] = None
country: Optional[str] = None
tags: Optional[List[str]] = None
@dataclass
class Deal:
title: str
value: float
contact_id: str
currency: str = "EUR"
stage: str = "qualification"
priority: str = "medium"
probability: int = 50
description: Optional[str] = None
expected_close_date: Optional[str] = None
source: str = "api"
class IsaoCRMSync:
def __init__(self, api_key: str, base_url: str = "https://staging.isao.io/api/external"):
self.api_key = api_key
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
})
# Rate limiting
self.last_request_time = 0
self.min_request_interval = 0.1 # 100ms between requests
def _rate_limit(self):
"""Ensure we don't exceed rate limits"""
elapsed = time.time() - self.last_request_time
if elapsed < self.min_request_interval:
time.sleep(self.min_request_interval - elapsed)
self.last_request_time = time.time()
def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict:
"""Make API request with error handling and rate limiting"""
self._rate_limit()
url = f"{self.base_url}{endpoint}"
try:
response = self.session.request(method, url, **kwargs)
if response.status_code == 429:
# Rate limited - wait and retry
retry_after = int(response.headers.get('Retry-After', 60))
logger.warning(f"Rate limited, waiting {retry_after} seconds")
time.sleep(retry_after)
return self._make_request(method, endpoint, **kwargs)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
logger.error(f"HTTP error {e.response.status_code}: {e.response.text}")
raise
except requests.exceptions.RequestException as e:
logger.error(f"Request error: {e}")
raise
def test_connection(self) -> bool:
"""Test API connection"""
try:
response = self._make_request("GET", "/test")
logger.info(f"✅ Connected to ISAO: {response['data']['companyId']}")
return True
except Exception as e:
logger.error(f"❌ Connection failed: {e}")
return False
def get_contacts(self, page: int = 1, limit: int = 200) -> Dict:
"""Get contacts with pagination"""
params = {"page": page, "limit": limit}
return self._make_request("GET", "/contacts", params=params)
def create_contact(self, contact: Contact) -> Dict:
"""Create a new contact"""
data = {
"name": contact.name,
"phoneNumber": contact.phone_number,
"email": contact.email,
"firstName": contact.first_name,
"lastName": contact.last_name,
"position": contact.position,
"currentCompany": contact.company,
"city": contact.city,
"country": contact.country,
"tags": contact.tags or []
}
# Remove None values
data = {k: v for k, v in data.items() if v is not None}
try:
response = self._make_request("POST", "/contacts", json=data)
logger.info(f"✅ Contact created: {response['data']['id']}")
return response["data"]
except requests.exceptions.HTTPError as e:
if e.response.status_code == 409:
# Contact already exists
existing_id = e.response.json().get("existingContactId")
logger.info(f"ℹ️ Contact exists: {existing_id}")
return self.get_contact(existing_id)
raise
def get_contact(self, contact_id: str) -> Dict:
"""Get contact by ID"""
response = self._make_request("GET", f"/contacts/{contact_id}")
return response["data"]
def update_contact(self, contact_id: str, updates: Dict) -> Dict:
"""Update existing contact"""
response = self._make_request("PUT", f"/contacts/{contact_id}", json=updates)
logger.info(f"✅ Contact updated: {contact_id}")
return response["data"]
def create_deal(self, deal: Deal) -> Dict:
"""Create a new deal"""
data = {
"title": deal.title,
"value": deal.value,
"currency": deal.currency,
"stage": deal.stage,
"priority": deal.priority,
"probability": deal.probability,
"contactId": deal.contact_id,
"description": deal.description,
"expectedCloseDate": deal.expected_close_date,
"source": deal.source
}
# Remove None values
data = {k: v for k, v in data.items() if v is not None}
response = self._make_request("POST", "/deals", json=data)
logger.info(f"✅ Deal created: {response['data']['id']}")
return response["data"]
def update_deal_stage(self, deal_id: str, stage: str, status: str = None) -> Dict:
"""Update deal stage and status"""
data = {"stage": stage}
if status:
data["status"] = status
if status in ["won", "lost"]:
data["actualCloseDate"] = datetime.now().isoformat()
response = self._make_request("PUT", f"/deals/{deal_id}/status", json=data)
logger.info(f"✅ Deal stage updated: {deal_id} -> {stage}")
return response["data"]
def sync_contacts_from_external(self, external_contacts: List[Dict]) -> Dict:
"""Sync contacts from external CRM"""
results = {"created": 0, "updated": 0, "errors": 0}
logger.info(f"📞 Starting sync of {len(external_contacts)} contacts")
for ext_contact in external_contacts:
try:
# Transform external contact to ISAO format
contact = self._transform_external_contact(ext_contact)
# Try to create, will return existing if duplicate
self.create_contact(contact)
results["created"] += 1
except Exception as e:
logger.error(f"❌ Failed to sync contact {ext_contact.get('id', 'unknown')}: {e}")
results["errors"] += 1
logger.info(f"📊 Sync completed: {results}")
return results
def _transform_external_contact(self, ext_contact: Dict) -> Contact:
"""Transform external CRM contact format to ISAO Contact"""
# This example assumes Salesforce format
return Contact(
name=f"{ext_contact.get('FirstName', '')} {ext_contact.get('LastName', '')}".strip(),
phone_number=ext_contact.get("Phone") or ext_contact.get("MobilePhone"),
email=ext_contact.get("Email"),
first_name=ext_contact.get("FirstName"),
last_name=ext_contact.get("LastName"),
position=ext_contact.get("Title"),
company=ext_contact.get("Account", {}).get("Name"),
city=ext_contact.get("MailingCity"),
country=ext_contact.get("MailingCountry"),
tags=ext_contact.get("Tags", "").split(",") if ext_contact.get("Tags") else []
)
def export_contacts_to_external(self, external_crm_client) -> Dict:
"""Export ISAO contacts to external CRM"""
results = {"exported": 0, "errors": 0}
page = 1
while True:
try:
response = self.get_contacts(page=page, limit=200)
contacts = response["data"]
if not contacts:
break
for contact in contacts:
try:
# Transform and export to external CRM
external_format = self._transform_to_external_contact(contact)
external_crm_client.create_contact(external_format)
results["exported"] += 1
except Exception as e:
logger.error(f"❌ Failed to export contact {contact['id']}: {e}")
results["errors"] += 1
# Check if there are more pages
if page >= response["meta"]["totalPages"]:
break
page += 1
except Exception as e:
logger.error(f"❌ Failed to fetch contacts page {page}: {e}")
break
logger.info(f"📤 Export completed: {results}")
return results
def _transform_to_external_contact(self, isao_contact: Dict) -> Dict:
"""Transform ISAO contact to external CRM format"""
# Example for Salesforce
return {
"FirstName": isao_contact.get("firstName") or isao_contact["name"].split()[0],
"LastName": isao_contact.get("lastName") or " ".join(isao_contact["name"].split()[1:]) or "Unknown",
"Email": isao_contact.get("email"),
"Phone": isao_contact.get("phoneNumber"),
"Title": isao_contact.get("position"),
"Company": isao_contact.get("currentCompany"),
"City": isao_contact.get("city"),
"Country": isao_contact.get("country"),
"Description": f"Imported from ISAO on {datetime.now().isoformat()}"
}
class BiDirectionalSync:
"""Handles bi-directional sync between ISAO and external CRM"""
def __init__(self, isao_client: IsaoCRMSync, external_crm_client):
self.isao = isao_client
self.external_crm = external_crm_client
self.last_sync_file = "last_sync.json"
def get_last_sync_time(self) -> datetime:
"""Get the last sync timestamp"""
try:
with open(self.last_sync_file, 'r') as f:
data = json.load(f)
return datetime.fromisoformat(data['last_sync'])
except FileNotFoundError:
# If no previous sync, sync last 7 days
return datetime.now() - timedelta(days=7)
def save_sync_time(self, sync_time: datetime):
"""Save the sync timestamp"""
with open(self.last_sync_file, 'w') as f:
json.dump({'last_sync': sync_time.isoformat()}, f)
def sync_incremental(self):
"""Perform incremental bi-directional sync"""
last_sync = self.get_last_sync_time()
current_time = datetime.now()
logger.info(f"🔄 Starting incremental sync since {last_sync}")
# Sync from external CRM to ISAO
try:
external_contacts = self.external_crm.get_updated_contacts(since=last_sync)
if external_contacts:
self.isao.sync_contacts_from_external(external_contacts)
except Exception as e:
logger.error(f"❌ Failed to sync from external CRM: {e}")
# Sync from ISAO to external CRM
try:
isao_response = self.isao.get_contacts()
# Filter by update date (would need API support for updatedSince)
# For now, we'll sync all and let the external CRM handle duplicates
updated_contacts = [
contact for contact in isao_response["data"]
if datetime.fromisoformat(contact["updatedAt"].replace('Z', '+00:00')) > last_sync
]
if updated_contacts:
for contact in updated_contacts:
try:
external_format = self.isao._transform_to_external_contact(contact)
self.external_crm.upsert_contact(external_format)
except Exception as e:
logger.error(f"❌ Failed to sync contact {contact['id']} to external CRM: {e}")
except Exception as e:
logger.error(f"❌ Failed to sync to external CRM: {e}")
# Save sync timestamp
self.save_sync_time(current_time)
logger.info("✅ Incremental sync completed")
# Example usage
def main():
# Initialize ISAO client
api_key = os.getenv("ISAO_API_KEY")
if not api_key:
logger.error("ISAO_API_KEY environment variable is required")
return
isao = IsaoCRMSync(api_key)
# Test connection
if not isao.test_connection():
return
# Example: Create contact
contact = Contact(
name="Python Test Contact",
phone_number="+33123456789",
email="python@example.com",
first_name="Python",
last_name="Tester",
position="Developer",
company="Tech Corp",
tags=["api", "python"]
)
created_contact = isao.create_contact(contact)
# Example: Create deal
deal = Deal(
title="Python Integration Deal",
value=25000.0,
contact_id=created_contact["id"],
description="Deal created via Python integration",
stage="proposal",
probability=60
)
created_deal = isao.create_deal(deal)
# Example: Update deal stage
time.sleep(2)
isao.update_deal_stage(created_deal["id"], "negotiation")
logger.info("✅ Python integration example completed")
if __name__ == "__main__":
main()
🌐 PHP E-commerce Integration
Integration example for WooCommerce or similar e-commerce platforms.
<?php
// IsaoEcommerceIntegration.php
require_once 'vendor/autoload.php';
class IsaoEcommerceIntegration {
private $apiKey;
private $baseUrl;
private $client;
private $logger;
public function __construct($apiKey, $baseUrl = 'https://staging.isao.io/api/external') {
$this->apiKey = $apiKey;
$this->baseUrl = $baseUrl;
$this->client = new GuzzleHttp\Client([
'base_uri' => $baseUrl,
'headers' => [
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json'
],
'timeout' => 30
]);
// Setup logging
$this->logger = new Monolog\Logger('isao-integration');
$this->logger->pushHandler(new Monolog\Handler\StreamHandler('isao-integration.log', Monolog\Logger::INFO));
}
public function testConnection() {
try {
$response = $this->client->get('/test');
$data = json_decode($response->getBody(), true);
$this->logger->info('✅ ISAO API connection successful', $data['data']);
return true;
} catch (Exception $e) {
$this->logger->error('❌ ISAO API connection failed', ['error' => $e->getMessage()]);
return false;
}
}
// Convert WooCommerce customer to ISAO contact
public function syncCustomerToContact($customer) {
try {
$contactData = [
'name' => trim($customer['first_name'] . ' ' . $customer['last_name']),
'firstName' => $customer['first_name'],
'lastName' => $customer['last_name'],
'phoneNumber' => $this->formatPhoneNumber($customer['billing']['phone']),
'email' => $customer['email'],
'city' => $customer['billing']['city'],
'country' => $customer['billing']['country'],
'tags' => ['ecommerce', 'customer']
];
// Add VIP tag for high-value customers
if ($this->getTotalOrderValue($customer['id']) > 1000) {
$contactData['tags'][] = 'vip';
}
$response = $this->client->post('/contacts', ['json' => $contactData]);
$contact = json_decode($response->getBody(), true)['data'];
$this->logger->info('✅ Customer synced to ISAO', [
'woo_customer_id' => $customer['id'],
'isao_contact_id' => $contact['id']
]);
// Store mapping for future use
$this->storeCustomerContactMapping($customer['id'], $contact['id']);
return $contact;
} catch (GuzzleHttp\Exception\ClientException $e) {
if ($e->getResponse()->getStatusCode() === 409) {
// Contact already exists
$errorData = json_decode($e->getResponse()->getBody(), true);
$existingContactId = $errorData['existingContactId'];
$this->storeCustomerContactMapping($customer['id'], $existingContactId);
$this->logger->info('ℹ️ Customer already exists in ISAO', [
'woo_customer_id' => $customer['id'],
'isao_contact_id' => $existingContactId
]);
return $this->getContact($existingContactId);
}
throw $e;
}
}
// Create deal from WooCommerce order
public function createDealFromOrder($order) {
try {
// Get or create contact for customer
$customer = $this->getWooCommerceCustomer($order['customer_id']);
$contact = $this->syncCustomerToContact($customer);
// Calculate deal value (total - tax - shipping)
$dealValue = floatval($order['total']) - floatval($order['total_tax']) - floatval($order['shipping_total']);
$dealData = [
'title' => "Order #{$order['number']}",
'description' => $this->generateOrderDescription($order),
'value' => $dealValue,
'currency' => strtoupper($order['currency']),
'stage' => $this->mapOrderStatusToStage($order['status']),
'priority' => $this->calculateOrderPriority($order),
'probability' => $this->calculateOrderProbability($order['status']),
'expectedCloseDate' => $this->calculateExpectedCloseDate($order),
'source' => 'ecommerce',
'contactId' => $contact['id']
];
$response = $this->client->post('/deals', ['json' => $dealData]);
$deal = json_decode($response->getBody(), true)['data'];
$this->logger->info('✅ Order converted to deal', [
'woo_order_id' => $order['id'],
'isao_deal_id' => $deal['id'],
'value' => $dealValue
]);
// Store mapping
$this->storeOrderDealMapping($order['id'], $deal['id']);
return $deal;
} catch (Exception $e) {
$this->logger->error('❌ Failed to create deal from order', [
'woo_order_id' => $order['id'],
'error' => $e->getMessage()
]);
throw $e;
}
}
// Update deal when order status changes
public function updateDealFromOrderStatus($orderId, $newStatus) {
try {
$dealId = $this->getOrderDealMapping($orderId);
if (!$dealId) {
$this->logger->warning('No deal found for order', ['order_id' => $orderId]);
return null;
}
$newStage = $this->mapOrderStatusToStage($newStatus);
$dealStatus = $this->mapOrderStatusToDealStatus($newStatus);
$updateData = ['stage' => $newStage];
if ($dealStatus) {
$updateData['status'] = $dealStatus;
if ($dealStatus !== 'open') {
$updateData['actualCloseDate'] = date('c'); // ISO 8601
}
}
$response = $this->client->put("/deals/{$dealId}/status", ['json' => $updateData]);
$deal = json_decode($response->getBody(), true)['data'];
$this->logger->info('✅ Deal updated from order status', [
'order_id' => $orderId,
'deal_id' => $dealId,
'new_stage' => $newStage,
'new_status' => $dealStatus
]);
return $deal;
} catch (Exception $e) {
$this->logger->error('❌ Failed to update deal from order status', [
'order_id' => $orderId,
'error' => $e->getMessage()
]);
throw $e;
}
}
// Sync abandoned carts as lost deals
public function syncAbandonedCarts() {
try {
$abandonedCarts = $this->getAbandonedCarts();
$results = ['processed' => 0, 'created' => 0, 'errors' => 0];
foreach ($abandonedCarts as $cart) {
try {
$results['processed']++;
// Skip if already processed
if ($this->isCartAlreadyProcessed($cart['id'])) {
continue;
}
// Get customer info
$customer = $this->getWooCommerceCustomer($cart['customer_id']);
$contact = $this->syncCustomerToContact($customer);
// Create deal for abandoned cart
$dealData = [
'title' => "Abandoned Cart #{$cart['id']}",
'description' => "Abandoned cart with " . count($cart['items']) . " items",
'value' => floatval($cart['total']),
'currency' => 'EUR',
'stage' => 'abandoned',
'status' => 'lost',
'priority' => 'low',
'probability' => 0,
'actualCloseDate' => date('c'),
'source' => 'ecommerce-abandoned',
'contactId' => $contact['id']
];
$response = $this->client->post('/deals', ['json' => $dealData]);
$deal = json_decode($response->getBody(), true)['data'];
$this->markCartAsProcessed($cart['id'], $deal['id']);
$results['created']++;
} catch (Exception $e) {
$results['errors']++;
$this->logger->error('❌ Failed to process abandoned cart', [
'cart_id' => $cart['id'],
'error' => $e->getMessage()
]);
}
}
$this->logger->info('📊 Abandoned cart sync completed', $results);
return $results;
} catch (Exception $e) {
$this->logger->error('❌ Abandoned cart sync failed', ['error' => $e->getMessage()]);
throw $e;
}
}
// Helper methods
private function formatPhoneNumber($phone) {
// Basic phone number formatting
$phone = preg_replace('/[^+\d]/', '', $phone);
if (!str_starts_with($phone, '+')) {
$phone = '+33' . ltrim($phone, '0'); // Assume French number
}
return $phone;
}
private function mapOrderStatusToStage($status) {
$mapping = [
'pending' => 'pending-payment',
'processing' => 'processing',
'on-hold' => 'on-hold',
'completed' => 'completed',
'cancelled' => 'cancelled',
'refunded' => 'refunded',
'failed' => 'failed'
];
return $mapping[$status] ?? 'unknown';
}
private function mapOrderStatusToDealStatus($status) {
$mapping = [
'completed' => 'won',
'cancelled' => 'lost',
'refunded' => 'lost',
'failed' => 'lost'
];
return $mapping[$status] ?? null; // null means keep as 'open'
}
private function calculateOrderPriority($order) {
$total = floatval($order['total']);
if ($total > 1000) return 'high';
if ($total > 500) return 'medium';
return 'low';
}
private function calculateOrderProbability($status) {
$probabilities = [
'pending' => 70,
'processing' => 90,
'on-hold' => 50,
'completed' => 100,
'cancelled' => 0,
'refunded' => 0,
'failed' => 0
];
return $probabilities[$status] ?? 70;
}
private function generateOrderDescription($order) {
$items = array_map(function($item) {
return $item['name'] . ' (x' . $item['quantity'] . ')';
}, $order['line_items']);
return "WooCommerce Order Items:\n" . implode("\n", $items);
}
private function calculateExpectedCloseDate($order) {
// For processing orders, expect completion in 3 days
if ($order['status'] === 'processing') {
return date('Y-m-d', strtotime('+3 days'));
}
// For pending orders, expect completion in 7 days
if ($order['status'] === 'pending') {
return date('Y-m-d', strtotime('+7 days'));
}
return null;
}
// Database helpers for storing mappings
private function storeCustomerContactMapping($customerId, $contactId) {
// Store in WordPress options or custom table
update_option("isao_customer_mapping_{$customerId}", $contactId);
}
private function getCustomerContactMapping($customerId) {
return get_option("isao_customer_mapping_{$customerId}", null);
}
private function storeOrderDealMapping($orderId, $dealId) {
update_option("isao_order_mapping_{$orderId}", $dealId);
}
private function getOrderDealMapping($orderId) {
return get_option("isao_order_mapping_{$orderId}", null);
}
// Mock methods - implement based on your WooCommerce setup
private function getWooCommerceCustomer($customerId) {
// Implement WooCommerce customer retrieval
return [
'id' => $customerId,
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'john@example.com',
'billing' => [
'phone' => '+33123456789',
'city' => 'Paris',
'country' => 'FR'
]
];
}
private function getTotalOrderValue($customerId) {
// Calculate total order value for customer
return 1500.0; // Mock value
}
private function getAbandonedCarts() {
// Get abandoned carts from WooCommerce
return []; // Mock data
}
private function isCartAlreadyProcessed($cartId) {
return get_option("isao_cart_processed_{$cartId}", false);
}
private function markCartAsProcessed($cartId, $dealId) {
update_option("isao_cart_processed_{$cartId}", $dealId);
}
}
// WordPress hooks integration
add_action('woocommerce_checkout_order_processed', function($orderId) {
try {
$integration = new IsaoEcommerceIntegration(get_option('isao_api_key'));
$order = wc_get_order($orderId);
$integration->createDealFromOrder($order->get_data());
} catch (Exception $e) {
error_log('ISAO Integration Error: ' . $e->getMessage());
}
});
add_action('woocommerce_order_status_changed', function($orderId, $oldStatus, $newStatus) {
try {
$integration = new IsaoEcommerceIntegration(get_option('isao_api_key'));
$integration->updateDealFromOrderStatus($orderId, $newStatus);
} catch (Exception $e) {
error_log('ISAO Integration Error: ' . $e->getMessage());
}
}, 10, 3);
// Daily cron job for abandoned carts
add_action('wp', function() {
if (!wp_next_scheduled('isao_sync_abandoned_carts')) {
wp_schedule_event(time(), 'daily', 'isao_sync_abandoned_carts');
}
});
add_action('isao_sync_abandoned_carts', function() {
try {
$integration = new IsaoEcommerceIntegration(get_option('isao_api_key'));
$integration->syncAbandonedCarts();
} catch (Exception $e) {
error_log('ISAO Abandoned Cart Sync Error: ' . $e->getMessage());
}
});
🔄 Lead Management Workflow
Complete workflow example showing lead capture to deal closure.
// leadWorkflow.js
class LeadManagementWorkflow {
constructor(isaoApiKey, emailService, crmService) {
this.isao = new IsaoAPI(isaoApiKey);
this.emailService = emailService;
this.crmService = crmService;
}
// 1. Capture lead from website form
async captureWebsiteLead(formData) {
try {
console.log('📝 Capturing website lead...');
// Create contact in ISAO
const contact = await this.isao.createContact({
name: `${formData.firstName} ${formData.lastName}`,
firstName: formData.firstName,
lastName: formData.lastName,
phoneNumber: formData.phone,
email: formData.email,
currentCompany: formData.company,
tags: ['website-lead', 'unqualified']
});
// Create initial deal
const deal = await this.isao.createDeal({
title: `${formData.company} - ${formData.interest}`,
description: `Website inquiry: ${formData.message}`,
value: this.estimateLeadValue(formData),
stage: 'lead',
priority: this.calculatePriority(formData),
probability: 20,
source: 'website',
contactId: contact.id
});
// Send welcome email
await this.emailService.sendWelcomeEmail(contact.email, {
firstName: contact.firstName,
company: contact.currentCompany
});
// Notify sales team
await this.notifySalesTeam(contact, deal, formData);
console.log('✅ Lead captured successfully:', deal.id);
return { contact, deal };
} catch (error) {
console.error('❌ Failed to capture lead:', error);
throw error;
}
}
// 2. Qualify lead based on scoring
async qualifyLead(dealId, qualificationData) {
try {
console.log('🔍 Qualifying lead...');
const score = this.calculateLeadScore(qualificationData);
const newStage = score >= 70 ? 'qualified' : 'nurturing';
const newProbability = Math.max(score, 20);
// Update deal
const deal = await this.isao.updateDealStatus(dealId, {
stage: newStage,
probability: newProbability
});
// Update contact tags
const contact = await this.isao.getContact(deal.contact.id);
const newTags = contact.tags.filter(tag => tag !== 'unqualified');
if (score >= 70) {
newTags.push('qualified', 'hot-lead');
} else {
newTags.push('nurturing', 'warm-lead');
}
await this.isao.updateContact(contact.id, { tags: newTags });
// Take action based on qualification
if (score >= 70) {
await this.handleQualifiedLead(deal, contact, qualificationData);
} else {
await this.handleNurturingLead(deal, contact);
}
console.log(`✅ Lead qualified with score ${score}: ${newStage}`);
return { deal, score, stage: newStage };
} catch (error) {
console.error('❌ Failed to qualify lead:', error);
throw error;
}
}
// 3. Handle qualified leads
async handleQualifiedLead(deal, contact, qualificationData) {
// Assign to sales rep
const salesRep = await this.assignSalesRep(qualificationData);
// Send detailed proposal
await this.emailService.sendProposal(contact.email, {
contactName: contact.name,
company: contact.currentCompany,
requirements: qualificationData.requirements,
estimatedValue: deal.value
});
// Create calendar booking link
const bookingLink = await this.createCalendarBooking(salesRep, contact);
// Send booking link email
await this.emailService.sendBookingLink(contact.email, {
salesRepName: salesRep.name,
bookingLink
});
// Update deal with sales rep assignment
await this.isao.updateDealStatus(deal.id, {
stage: 'proposal',
probability: 60
});
console.log('📞 Qualified lead assigned to sales rep:', salesRep.name);
}
// 4. Handle nurturing leads
async handleNurturingLead(deal, contact) {
// Add to email nurturing sequence
await this.emailService.addToNurturingSequence(contact.email, {
contactName: contact.name,
company: contact.currentCompany,
interests: contact.tags
});
// Schedule follow-up in 1 week
await this.scheduleFollowUp(deal.id, 7);
console.log('📧 Lead added to nurturing sequence');
}
// 5. Progress deal through pipeline
async progressDeal(dealId, newStage, notes) {
try {
const stageConfig = {
'discovery': { probability: 40, action: 'sendDiscoveryQuestions' },
'proposal': { probability: 60, action: 'generateProposal' },
'negotiation': { probability: 80, action: 'scheduleMeeting' },
'closed-won': { probability: 100, status: 'won', action: 'sendWelcome' },
'closed-lost': { probability: 0, status: 'lost', action: 'sendFeedbackSurvey' }
};
const config = stageConfig[newStage];
if (!config) {
throw new Error(`Unknown stage: ${newStage}`);
}
// Update deal
const updateData = {
stage: newStage,
probability: config.probability
};
if (config.status) {
updateData.status = config.status;
updateData.actualCloseDate = new Date().toISOString();
}
const deal = await this.isao.updateDealStatus(dealId, updateData);
// Add notes to CRM
if (notes) {
await this.crmService.addNotes(dealId, notes);
}
// Execute stage-specific action
if (config.action) {
await this[config.action](deal);
}
console.log(`✅ Deal progressed to ${newStage}:`, dealId);
return deal;
} catch (error) {
console.error('❌ Failed to progress deal:', error);
throw error;
}
}
// Helper methods
calculateLeadScore(data) {
let score = 0;
// Company size scoring
if (data.companySize === 'enterprise') score += 30;
else if (data.companySize === 'medium') score += 20;
else if (data.companySize === 'small') score += 10;
// Budget scoring
if (data.budget === 'high') score += 25;
else if (data.budget === 'medium') score += 15;
else if (data.budget === 'low') score += 5;
// Timeline scoring
if (data.timeline === 'immediate') score += 25;
else if (data.timeline === '3months') score += 20;
else if (data.timeline === '6months') score += 10;
// Authority scoring
if (data.decisionMaker) score += 20;
return Math.min(score, 100);
}
estimateLeadValue(formData) {
const basePricing = {
'starter': 5000,
'professional': 15000,
'enterprise': 50000,
'custom': 25000
};
return basePricing[formData.interest] || 10000;
}
calculatePriority(formData) {
const urgentKeywords = ['urgent', 'asap', 'immediately'];
const message = formData.message?.toLowerCase() || '';
if (urgentKeywords.some(keyword => message.includes(keyword))) {
return 'high';
}
return formData.budget === 'high' ? 'high' : 'medium';
}
async assignSalesRep(qualificationData) {
// Simple round-robin assignment
const salesReps = [
{ id: 'rep1', name: 'Alice Johnson', email: 'alice@company.com' },
{ id: 'rep2', name: 'Bob Wilson', email: 'bob@company.com' }
];
const randomIndex = Math.floor(Math.random() * salesReps.length);
return salesReps[randomIndex];
}
// Stage-specific actions
async sendDiscoveryQuestions(deal) {
await this.emailService.sendDiscoveryQuestions(deal.contact.email, {
dealTitle: deal.title,
contactName: deal.contact.name
});
}
async generateProposal(deal) {
const proposal = await this.generateProposalDocument(deal);
await this.emailService.sendProposal(deal.contact.email, {
proposalLink: proposal.link,
contactName: deal.contact.name
});
}
async scheduleMeeting(deal) {
const meetingLink = await this.createMeetingInvite(deal);
await this.emailService.sendMeetingInvite(deal.contact.email, {
meetingLink,
dealTitle: deal.title
});
}
async sendWelcome(deal) {
await this.emailService.sendCustomerWelcome(deal.contact.email, {
contactName: deal.contact.name,
dealValue: deal.value
});
// Sync to customer success system
await this.crmService.createCustomerRecord(deal);
}
async sendFeedbackSurvey(deal) {
await this.emailService.sendFeedbackSurvey(deal.contact.email, {
contactName: deal.contact.name,
reasonForLoss: deal.lossReason
});
}
}
// Usage example
const workflow = new LeadManagementWorkflow(
process.env.ISAO_API_KEY,
new EmailService(),
new CRMService()
);
// Website form submission
app.post('/api/contact-form', async (req, res) => {
try {
const result = await workflow.captureWebsiteLead(req.body);
res.json({ success: true, dealId: result.deal.id });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Lead qualification endpoint
app.post('/api/qualify-lead/:dealId', async (req, res) => {
try {
const result = await workflow.qualifyLead(req.params.dealId, req.body);
res.json({ success: true, ...result });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
This comprehensive documentation provides everything needed for successful API integration and Zapier approval, including:
- Complete authentication guide with security best practices
- Detailed API documentation for contacts and deals
- Full webhook implementation for real-time integrations
- Comprehensive error handling and best practices
- Complete code examples in multiple languages
- End-to-end workflow examples showing real-world usage
The documentation is production-ready and follows industry standards that Zapier and other platforms expect for approval.