Bidirectional Account sync at 15K records daily is well within Salesforce bulk API thresholds, but the architecture decisions you make upfront on external IDs and conflict resolution will define your data quality ceiling long-term.
Field Mapping Strategy
Create a canonical transformation layer in your middleware (MuleSoft, Boomi, or custom ETL) — never map directly field-to-field between systems. This gives you a single place to manage type coercions, picklist translations, and null handling.
Key patterns:
- Data type mismatches: Handle phone/date normalization in the transformation layer before hitting either API. Salesforce’s Phone field is a free-text string; coerce legacy formats there.
- Picklist divergence: Maintain a lookup table in middleware mapping legacy enum values to Salesforce picklist API values. Reject unmapped values and route to a dead-letter queue rather than silently dropping data.
- Required field gaps: If the legacy CRM doesn’t populate Salesforce-required fields, define default injection rules in your transformer, not in Salesforce validation rules (which will just throw errors mid-batch).
External ID Management
Create a dedicated External ID field on the Account object — type Text, marked Unique and External ID in field settings. Use your legacy CRM’s primary key as the value.
Object: Account
Field API Name: Legacy_CRM_ID__c
Field Type: Text(18)
Unique: true (case-insensitive)
External ID: true
Use Upsert operations via the Bulk API 2.0 /jobs/ingest endpoint with externalIdFieldName=Legacy_CRM_ID__c. This prevents duplicate Account creation on retry scenarios.
POST /services/data/v59.0/jobs/ingest
{
"object": "Account",
"operation": "upsert",
"externalIdFieldName": "Legacy_CRM_ID__c",
"contentType": "CSV"
}
Verify endpoint version against your org’s API version — v59.0 used here as example.
Conflict Resolution
Last-write-wins is the default and will corrupt data in bidirectional sync. Implement timestamp-based conflict detection:
- Store
LastModifiedDate from both systems in your middleware state store (Redis or database table) after each sync cycle.
- On inbound record, compare incoming
LastModifiedDate against your stored last-sync timestamp per record.
- If both systems show modification since last sync cycle, apply a field-level merge rather than record-level overwrite — Salesforce-owned fields win on Salesforce side, legacy-owned fields win on legacy side.
- Flag true conflicts (same field modified in both systems) to a review queue via Platform Events or a custom
Sync_Conflict__c object.
Define field ownership explicitly per field in your mapping config — avoid shared ownership on any single field.
Error Handling Pattern
Use Bulk API 2.0’s built-in failed results CSV (/jobs/ingest/{jobId}/failedResults) for batch error extraction. Don’t rely solely on job-level status.
Implement exponential backoff with jitter for UNABLE_TO_LOCK_ROW and REQUEST_LIMIT_EXCEEDED errors. DUPLICATE_VALUE on upsert indicates your External ID field isn’t populated correctly on the inbound record — treat as a data quality alert, not a retry candidate.
Monitor API usage via /services/data/vXX.X/limits — 15K daily upserts is low volume, but factor in retry headroom against your org’s daily API limit allocation (verify your org edition limits).
This draft is based on general Salesforce knowledge. It has not been verified against your specific version and environment. Practitioners: verify the steps and share your experience below.