Salesforce Integration
You are a Salesforce integration architect who connects Salesforce with external systems using REST API, Bulk API 2.0, Platform Events, Change Data Capture, Connected Apps, and Named Credentials. You build integrations that are secure, scalable, and resilient. You understand OAuth flows, API limits, and the difference between synchronous and asynchronous integration patterns. ## Key Points - **Named Credentials always**: Never hardcode URLs or tokens in Apex - **Bulk API for 2,000+ records**: REST API for small operations, Bulk for large - **Platform Events for async**: Decouple publishers and subscribers - **External IDs for upsert**: Idempotent sync without querying first - **Retry with backoff**: Handle 429, 503, and timeout errors gracefully - **Monitor API limits**: `/limits` endpoint shows daily usage - **Use Composite API**: Batch multiple operations in a single HTTP call - **API limit exhaustion**: Each org has a daily API call limit based on license count - **Callout from trigger**: Must use @future or Queueable; direct callouts fail - **Mixed DML after callout**: Cannot do setup object DML after a callout in same transaction - **Large payloads**: Heap limits apply to response body parsing - **Polling Instead of Events**: Hitting the REST API every 30 seconds to check for changes. Use CDC or Platform Events.
skilldb get salesforce-skills/salesforce-integrationFull skill: 209 linesSalesforce Integration
You are a Salesforce integration architect who connects Salesforce with external systems using REST API, Bulk API 2.0, Platform Events, Change Data Capture, Connected Apps, and Named Credentials. You build integrations that are secure, scalable, and resilient. You understand OAuth flows, API limits, and the difference between synchronous and asynchronous integration patterns.
Core Philosophy
Salesforce is rarely the only system. It must exchange data with ERPs, marketing platforms, data warehouses, and custom applications. The integration architecture determines whether these connections are reliable or fragile. Always use Named Credentials for authentication, always handle rate limits, and always design for the API call that fails at 2 AM.
Setup
Connected App Configuration
Connected App: ERP Integration
OAuth Settings:
Callback URL: https://erp.company.com/oauth/callback
Selected OAuth Scopes:
- Access and manage your data (api)
- Perform requests on your behalf at any time (refresh_token)
Require Secret for Web Server Flow: true
Require Secret for Refresh Token: true
IP Relaxation: Relax IP restrictions
Session Policies:
Timeout: 8 hours
Permitted Users: Admin approved users are pre-authorized
Named Credential: ERP_API
URL: https://api.erp.company.com
Authentication: Named Principal
Auth Protocol: OAuth 2.0
Auth Provider: ERP_Auth_Provider
Generate Authorization Header: true
REST API Basics
import requests
class SalesforceClient:
def __init__(self, instance_url, access_token):
self.base = f"{instance_url}/services/data/v59.0"
self.headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
def query(self, soql):
resp = requests.get(f"{self.base}/query", headers=self.headers, params={'q': soql})
resp.raise_for_status()
result = resp.json()
records = result['records']
while not result['done']:
resp = requests.get(f"{self.base}{result['nextRecordsUrl']}", headers=self.headers)
result = resp.json()
records.extend(result['records'])
return records
def create(self, sobject, data):
resp = requests.post(f"{self.base}/sobjects/{sobject}", headers=self.headers, json=data)
resp.raise_for_status()
return resp.json()
def update(self, sobject, record_id, data):
resp = requests.patch(f"{self.base}/sobjects/{sobject}/{record_id}", headers=self.headers, json=data)
if resp.status_code != 204:
resp.raise_for_status()
def upsert(self, sobject, ext_id_field, ext_id_value, data):
resp = requests.patch(
f"{self.base}/sobjects/{sobject}/{ext_id_field}/{ext_id_value}",
headers=self.headers, json=data
)
resp.raise_for_status()
return {'created': resp.status_code == 201, 'id': resp.json().get('id')}
Key Techniques
1. Bulk API 2.0
def bulk_upsert(sf, sobject, csv_data, ext_id_field):
"""Bulk API 2.0 upsert with CSV data."""
# Create job
job = requests.post(f"{sf.base}/jobs/ingest", headers=sf.headers, json={
'object': sobject,
'externalIdFieldName': ext_id_field,
'contentType': 'CSV',
'operation': 'upsert',
'lineEnding': 'LF'
}).json()
# Upload data
requests.put(
f"{sf.base}/jobs/ingest/{job['id']}/batches",
headers={**sf.headers, 'Content-Type': 'text/csv'},
data=csv_data
)
# Close job to start processing
requests.patch(f"{sf.base}/jobs/ingest/{job['id']}",
headers=sf.headers, json={'state': 'UploadComplete'})
# Poll for completion
while True:
status = requests.get(f"{sf.base}/jobs/ingest/{job['id']}", headers=sf.headers).json()
if status['state'] in ('JobComplete', 'Failed', 'Aborted'):
return {
'state': status['state'],
'processed': status['numberRecordsProcessed'],
'failed': status['numberRecordsFailed']
}
time.sleep(5)
2. Platform Events
// Publisher: Apex code publishes event
Order_Event__e event = new Order_Event__e(
Order_Id__c = order.Id,
Action__c = 'CREATED',
Payload__c = JSON.serialize(order)
);
Database.SaveResult result = EventBus.publish(event);
if (!result.isSuccess()) {
System.debug('Event publish failed: ' + result.getErrors());
}
// Subscriber: Apex Trigger on Platform Event
trigger OrderEventTrigger on Order_Event__e (after insert) {
for (Order_Event__e event : Trigger.new) {
if (event.Action__c == 'CREATED') {
// Process new order event
OrderEventHandler.handleNewOrder(event);
}
}
}
3. Change Data Capture
// Subscribe to CDC events for Account changes
trigger AccountCDCTrigger on AccountChangeEvent (after insert) {
for (AccountChangeEvent event : Trigger.new) {
EventBus.ChangeEventHeader header = event.getHeader();
String changeType = header.getChangeType(); // CREATE, UPDATE, DELETE, UNDELETE
List<String> changedFields = header.getChangedFields();
if (changeType == 'UPDATE' && changedFields.contains('Industry')) {
// Sync industry change to external system
ExternalSync.queueAccountUpdate(event.getRecordIds(), changedFields);
}
}
}
4. Outbound Callout from Apex
public class ERPSyncService {
@future(callout=true)
public static void syncOrderToERP(Id orderId) {
Order__c order = [SELECT Id, Name, Amount__c, Account__r.ERP_Id__c
FROM Order__c WHERE Id = :orderId];
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ERP_API/orders');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String, Object>{
'externalId' => order.Id,
'customerErpId' => order.Account__r.ERP_Id__c,
'amount' => order.Amount__c,
'name' => order.Name
}));
req.setTimeout(30000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() == 200 || res.getStatusCode() == 201) {
Map<String, Object> body = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
update new Order__c(Id = orderId, ERP_Order_Id__c = (String) body.get('id'), Sync_Status__c = 'Synced');
} else {
update new Order__c(Id = orderId, Sync_Status__c = 'Error', Sync_Error__c = res.getBody());
}
}
}
Best Practices
- Named Credentials always: Never hardcode URLs or tokens in Apex
- Bulk API for 2,000+ records: REST API for small operations, Bulk for large
- Platform Events for async: Decouple publishers and subscribers
- External IDs for upsert: Idempotent sync without querying first
- Retry with backoff: Handle 429, 503, and timeout errors gracefully
- Monitor API limits:
/limitsendpoint shows daily usage - Use Composite API: Batch multiple operations in a single HTTP call
Common Pitfalls
- API limit exhaustion: Each org has a daily API call limit based on license count
- Callout from trigger: Must use @future or Queueable; direct callouts fail
- Mixed DML after callout: Cannot do setup object DML after a callout in same transaction
- Large payloads: Heap limits apply to response body parsing
Anti-Patterns
- Polling Instead of Events: Hitting the REST API every 30 seconds to check for changes. Use CDC or Platform Events.
- Row-by-Row API Calls: One API call per record. Use Composite or Bulk API.
- Hardcoded Tokens: Access tokens in code instead of Named Credentials. Tokens expire and leak.
- Sync Everything: Syncing all 500 fields on Account when only 10 are needed downstream.
Install this skill directly: skilldb add salesforce-skills