🔌 Salesforce Integration
🎯 Overview
Salesforce is the world's leading enterprise CRM platform providing comprehensive customer relationship management, sales automation, service cloud, and analytics. The DashClicks integration enables OAuth-based authentication and data export for contacts, opportunities (deals), and notes from Salesforce CRM.
Provider: Salesforce, Inc. (https://www.salesforce.com)
API Version: v48.0 (configurable)
Integration Type: OAuth 2.0 REST API with SOQL (Salesforce Object Query Language)
📁 Directory Structure
Documentation Structure:
salesforce/
├── 📄 index.md - Integration overview and setup
├── 📄 authentication.md - OAuth 2.0 flow and token management
└── 📄 crm-data.md - CRM data export (contacts, deals, notes) with pagination
Source Code Location:
- Base Path:
external/Integrations/Salesforce/ - Controllers:
Controllers/authController.js,Controllers/exportController.js,Controllers/pagination.js - Providers:
Providers/api.js - Routes:
Routes/authRoutes.js,Routes/exportRoutes.js - Models:
Models/keys.js
🗄️ MongoDB Collections
📚 Detailed Schema: See Database Collections Documentation
integrations.salesforce.key
- Purpose: Store OAuth access tokens and refresh tokens for Salesforce API authentication
- Model:
shared/models/salesforce-key.js - Primary Use: Persist Salesforce OAuth credentials for authenticated API requests
Schema Structure:
{
_id: ObjectId,
account_id: String, // DashClicks account ID
owner: String, // User ID who connected the integration
token: {
access_token: String, // Short-lived access token
refresh_token: String, // Long-lived refresh token (never expires unless revoked)
issued_at: String // Timestamp when token was issued (Unix timestamp as string)
},
token_invalidated: Boolean // Flag indicating manual token invalidation
}
The Salesforce key model uses {strict: false} schema option, allowing additional fields from Salesforce OAuth responses to be stored dynamically.
🔐 Authentication & Configuration
Authentication Method: OAuth 2.0 Authorization Code Grant
Required Environment Variables:
| Variable | Description | Required |
|---|---|---|
SALESFORCE_CLIENT_ID | OAuth application client ID | ✅ |
SALESFORCE_SECRET_ID | OAuth application client secret | ✅ |
SALESFORCE_AUTH_SCOPES | OAuth permission scopes | ✅ |
SALESFORCE_AUTH_URL | OAuth authorization endpoint | ✅ |
SALESFORCE_TOKEN_URL | OAuth token exchange endpoint | ✅ |
SALESFORCE_GRANT_TYPE | Grant type (authorization_code) | ✅ |
SALESFORCE_REDIRECT_URL | OAuth callback URL | ✅ |
SALESFORCE_CONTACT_ENDPOINT | SOQL query for contacts | ✅ |
SALESFORCE_DEALS_ENDPOINT | SOQL query for opportunities | ✅ |
SALESFORCE_NOTES_ENDPOINT | SOQL query for notes | ✅ |
SALESFORCE_COUNT_ENDPOINT | Base endpoint for COUNT queries | ✅ |
Example Configuration:
SALESFORCE_CLIENT_ID=3MVG97quAmFZJfVxWTwCFgzaduZWToVcHwRjxykW0U4.k_KVogJz1IsKlf7HXupdgw0eWCja8iOUZ91JQ9R98
SALESFORCE_SECRET_ID=CC7AC2A9674C0A7E25D159FA868DFE7399D462CC7D58D61F1D558D23E97B34A8
SALESFORCE_REDIRECT_URL=http://localhost:5000/v1/integrations/salesforce/auth/callback
SALESFORCE_AUTH_SCOPES=api%20id%20web%20refresh_token
SALESFORCE_AUTH_URL=https://login.salesforce.com/services/oauth2/authorize
SALESFORCE_TOKEN_URL=https://login.salesforce.com/services/oauth2/token
SALESFORCE_GRANT_TYPE=authorization_code
# SOQL Query Endpoints (v48.0)
SALESFORCE_CONTACT_ENDPOINT=https://na1.salesforce.com/services/data/v48.0/query/?q=SELECT+name,+account.name,+title,+phone,+email,+contact.owner.alias+from+contact
SALESFORCE_DEALS_ENDPOINT=https://na1.salesforce.com/services/data/v48.0/query?q=select+name,+account.name,+amount,+CloseDate,+StageName,+opportunity.owner.alias+from+opportunity
SALESFORCE_NOTES_ENDPOINT=https://na1.salesforce.com/services/data/v48.0/query?q=select+body,+IsDeleted,+IsPrivate,+OwnerId,+ParentId,+Title,+note.owner.alias+from+note
SALESFORCE_COUNT_ENDPOINT=https://ap5.salesforce.com/services/data/v48.0/query?q=SELECT+count()+FROM
Credential Storage: OAuth tokens stored in integrations.salesforce.key collection
Salesforce uses instance-specific URLs (e.g., na1, ap5). Update endpoints based on your Salesforce org's instance.
🏗️ Architecture Overview
Key Responsibilities:
- OAuth Authentication: Manage OAuth 2.0 authorization flow with JWT state tokens (1-hour expiration)
- SOQL Queries: Execute Salesforce Object Query Language queries for data retrieval
- Pagination: Custom pagination system with page/limit parameters and total record counts
- Token Refresh: Automatic refresh token handling when access tokens expire
- Data Export: Retrieve contacts, opportunities (deals), and notes with custom field selection
API Communication Pattern:
- Protocol: REST API over HTTPS
- Authentication: Bearer token in Authorization header
- Query Language: SOQL (Salesforce Object Query Language) via REST API
- Data Format: JSON response
- Pagination: Offset-based with LIMIT and OFFSET clauses in SOQL
Rate Limiting:
- Salesforce Limits: Varies by edition (Enterprise: 1,000 API calls/24h per user, Unlimited: 5,000+)
- Handling: No automatic rate limiting in current implementation
- Recommendation: Monitor API usage via Salesforce Setup → System Overview
🔗 Features & Capabilities
Core Features
- 📘 Authentication & Data - OAuth 2.0 flow and CRM data synchronization
🔄 Integration Data Flow
sequenceDiagram
participant Client as DashClicks Frontend
participant Router as API Router
participant Auth as Auth Controller
participant Export as Export Controller
participant Provider as Salesforce Provider
participant DB as MongoDB
participant Salesforce as Salesforce API
Client->>Router: GET /v1/integrations/salesforce/auth/login
Router->>Auth: Verify scope & access
Auth->>DB: Check existing token
alt Token exists and valid
Auth-->>Client: Redirect to success URL
else Token missing/invalid
Auth->>Auth: Generate JWT state token (1h exp)
Auth-->>Client: Redirect to Salesforce OAuth
Client->>Salesforce: User authorizes app
Salesforce-->>Auth: Redirect with code
Auth->>Provider: Exchange code for tokens
Provider->>Salesforce: POST /oauth/v1/token
Salesforce-->>Provider: Access + refresh tokens
Provider->>DB: Store tokens
Auth-->>Client: Redirect to success URL
end
Note over Client,Salesforce: Data Export Flow
Client->>Router: GET /export/contacts?page=1&limit=100
Router->>Export: exportData()
Export->>DB: Fetch stored token
DB-->>Export: Return access token
Export->>Salesforce: GET /query (COUNT query)
Salesforce-->>Export: Total record count
Export->>Export: Calculate pagination
Export->>Salesforce: GET /query (with LIMIT + OFFSET)
alt Token valid
Salesforce-->>Export: Contact records
Export-->>Client: Data + pagination
else Token expired (401)
Export->>Provider: getRefreshAccessToken()
Provider->>Salesforce: POST /token (refresh grant)
Salesforce-->>Provider: New access token
Provider->>DB: Update token
Export->>Salesforce: Retry query with new token
Salesforce-->>Export: Contact records
Export-->>Client: Data + pagination
end
🔗 API Endpoints
Authentication Endpoints
| Endpoint | Method | Scope | Description |
|---|---|---|---|
/v1/integrations/salesforce/auth/login | GET | salesforce, salesforce.create | Initialize OAuth flow |
/v1/integrations/salesforce/auth/callback | GET | None | OAuth callback (auto-redirect) |
/v1/integrations/salesforce/auth/ | DELETE | salesforce, salesforce.delete | Delete OAuth token |
Data Export Endpoints
| Endpoint | Method | Scope | Query Params | Description |
|---|---|---|---|---|
/v1/integrations/salesforce/export/:type | GET | salesforce, salesforce.read | page, limit | Export CRM data |
Supported Types:
contacts- Export Salesforce contactsdeals- Export Salesforce opportunities (deals)notes- Export Salesforce notes
Query Parameters:
page(Number, optional) - Page number (default: 1)limit(Number, optional) - Records per page (default: all records)
Export endpoint uses dynamic :type parameter, allowing a single route to handle multiple CRM data types.
📊 Response Format
All data export endpoints return a standardized response structure:
{
"success": true,
"message": "SUCCESS",
"data": [...], // Array of CRM records
"pagination": {
"page": 1,
"per_page": 100,
"total": 1500,
"total_pages": 15,
"next_page": 2,
"prev_page": null,
"offsetValue": 0
}
}
Pagination Fields:
page- Current page numberper_page- Records per pagetotal- Total number of records across all pagestotal_pages- Total number of pagesnext_page- Next page number (null if last page)prev_page- Previous page number (null if first page)offsetValue- SOQL OFFSET value for current page
🎯 SOQL Query Structure
Contacts Query
SELECT name, account.name, title, phone, email, contact.owner.alias
FROM contact
LIMIT {per_page} OFFSET {offsetValue}
Fields Retrieved:
name- Contact full nameaccount.name- Associated account name (relationship)title- Job titlephone- Phone numberemail- Email addresscontact.owner.alias- Owner alias (user who owns the contact)
Opportunities (Deals) Query
SELECT name, account.name, amount, CloseDate, StageName, opportunity.owner.alias
FROM opportunity
LIMIT {per_page} OFFSET {offsetValue}
Fields Retrieved:
name- Opportunity nameaccount.name- Associated account nameamount- Deal amount (currency)CloseDate- Expected close dateStageName- Current sales stageopportunity.owner.alias- Owner alias
Notes Query
SELECT body, IsDeleted, IsPrivate, OwnerId, ParentId, Title, note.owner.alias
FROM note
LIMIT {per_page} OFFSET {offsetValue}
Fields Retrieved:
body- Note content/textIsDeleted- Soft delete flagIsPrivate- Privacy flagOwnerId- ID of user who created the noteParentId- ID of related record (contact, account, etc.)Title- Note titlenote.owner.alias- Owner alias
To retrieve custom fields, modify the SOQL queries in environment variables. Custom fields end with __c (e.g., Custom_Field__c).
🚨 Error Handling
Common Error Scenarios:
| Error Code | Scenario | Handling |
|---|---|---|
| 401 | Invalid/expired access token | Automatic token refresh attempted |
| 404 | Token not found in database | User redirected to re-authenticate |
| 400 | Invalid type parameter | Error message with supported types |
| 400 | Invalid SOQL query | Salesforce error returned to client |
Salesforce-Specific Errors:
// INVALID_SESSION_ID (401)
[
{
"message": "Session expired or invalid",
"errorCode": "INVALID_SESSION_ID"
}
]
Token Invalidation:
When token_invalidated: true is set on a stored token, the integration automatically deletes the token and forces re-authentication.
📊 Monitoring & Logging
Token Lifecycle Tracking:
issued_atfield tracks when tokens were issued (Unix timestamp)- Refresh tokens never expire unless manually revoked
- Token expiration managed automatically via refresh token flow
Recommended Monitoring:
- Track OAuth callback failures
- Monitor Salesforce API usage limits (Setup → System Overview)
- Alert on 401 errors indicating token issues
- Log SOQL query performance
🎯 Integration Use Cases
Contact Synchronization
Import Salesforce contacts with pagination:
GET /v1/integrations/salesforce/export/contacts?page=1&limit=200
Opportunity Pipeline Analysis
Export all opportunities for reporting:
GET /v1/integrations/salesforce/export/deals?page=1&limit=500
Activity History
Fetch all notes for contact history:
GET /v1/integrations/salesforce/export/notes?page=1&limit=100
🔗 Related Documentation
- Provider API Docs: Salesforce REST API Documentation
- OAuth Guide: Salesforce OAuth 2.0
- SOQL Reference: SOQL and SOSL Reference
- Object Reference: Standard Objects
⚠️ Implementation Notes
OAuth State Management
- JWT tokens used for state parameter with 1-hour expiration
- State includes
account_id,owner, andforward_url - Prevents CSRF attacks by validating state on callback
Token Refresh Strategy
- Access tokens expire after session timeout (configurable in Salesforce org)
- Refresh tokens never expire unless manually revoked
- Automatic refresh on 401 INVALID_SESSION_ID error
- Updated tokens saved back to database immediately
Pagination System
- Custom pagination utility (
Controllers/pagination.js) - Requires two API calls: COUNT query + data query
- LIMIT and OFFSET clauses appended to SOQL queries
- Default behavior: Return all records if limit not specified
SOQL Considerations
- Field Selection: Only specified fields are retrieved
- Relationships: Use dot notation (e.g.,
account.name) - Custom Fields: End with
__csuffix - API Version: Specified in endpoint URLs (v48.0)
Scope Requirements
All endpoints require one of:
salesforcescope - Full Salesforce integration accesssalesforce.createscope - Create integrationsalesforce.readscope - Read datasalesforce.deletescope - Delete integration
These scopes are enforced via verifyScope middleware.
🚀 Getting Started
1. Configure OAuth App
Create Connected App in Salesforce Setup and configure environment variables with callback URL.
2. Initiate OAuth Flow
Direct users to:
GET /v1/integrations/salesforce/auth/login?forward_url=https://app.dashclicks.com/success
3. Export Data
Once authenticated, export data using:
GET /v1/integrations/salesforce/export/{type}?page=1&limit=100
Where {type} is one of: contacts, deals, notes
4. Handle Pagination
Use returned pagination.next_page for subsequent requests:
GET /v1/integrations/salesforce/export/contacts?page=2&limit=100
Continue until pagination.next_page is null.
5. Customize SOQL Queries
Modify environment variables to add custom fields:
SALESFORCE_CONTACT_ENDPOINT=https://na1.salesforce.com/services/data/v48.0/query/?q=SELECT+name,+email,+Custom_Field__c+from+contact
📈 Performance Considerations
API Usage Optimization
- Use appropriate
limitvalues to reduce API call count - Salesforce charges per API call - monitor usage in Setup
- Consider caching frequently accessed data
- Use bulk queries when possible
SOQL Query Optimization
- Select only required fields (avoid
SELECT *equivalent) - Use indexed fields in WHERE clauses when filtering
- Avoid complex relationship queries when possible
- Consider Salesforce governor limits (50,000 rows max)
Token Management
- Refresh tokens are long-lived - store securely
- Access tokens are session-based - refresh on 401 errors
- Consider implementing token revocation on user logout