Contact API upsert fails on duplicate email when using external ID for matching

We’re integrating our marketing platform with Salesforce using REST API to sync contact records. We’ve set up a custom external ID field (External_Contact_ID__c) on the Contact object and are using upsert operations to create or update contacts based on this field.

The problem: When we try to upsert a contact with an email that already exists in Salesforce but with a different External_Contact_ID__c, the operation fails with a duplicate rule error:


DUPLICATE_VALUE: duplicate value found
Email field: existing contact has same email
External ID: values don't match

Our upsert logic uses the external ID as the key, but Salesforce’s standard duplicate rules on Email are blocking the operation. We need the external ID to be the source of truth for matching records, but we also want to prevent actual duplicate contacts.

How do we configure duplicate rules and REST API upsert logic to handle this scenario properly? We’re on Spring '24 and processing about 5,000 contact syncs daily.

The issue is that duplicate rules and upsert logic operate independently. When you upsert by External_Contact_ID__c, Salesforce first checks if that external ID exists. If not, it tries to create a new record, which then triggers duplicate rules checking the email field. You need to either modify your duplicate rule to exclude API operations, or implement a two-step process: query by email first, then decide whether to create or update based on what you find.

I’d recommend using the AllowSave alert action in your duplicate rule instead of Block. This lets the upsert proceed but creates a duplicate record set that you can review. Alternatively, you could use matching rules without duplicate rules - configure a matching rule on Email and External ID, then handle the logic in your integration code to determine which contact to update.

This is a common integration challenge that requires careful configuration of both Contact duplicate rule settings and REST API error handling. Here’s the complete solution:

1. Contact Duplicate Rule Configuration - Create Custom Rule for API Operations:

The standard duplicate rules don’t understand external ID context. Create a custom duplicate rule specifically for API integrations:

In Setup → Duplicate Rules → New Rule:

  • Object: Contact
  • Record-Level Security: Bypass sharing
  • Action on Create: Allow (not Block)
  • Action on Edit: Allow
  • Alert Text: “Potential duplicate - same email, different external ID”

Then create a matching rule that considers both fields:

  • Matching Rule: Contact_API_Matching
  • Match on: Email (Exact) AND External_Contact_ID__c (Exact)
  • Match blank fields: No

This allows upserts by external ID while still detecting email duplicates for manual review.

2. External ID Upsert Logic - Implement Proper Error Handling:

Your REST API integration needs to handle duplicate scenarios gracefully:

PATCH /services/data/v60.0/sobjects/Contact/External_Contact_ID__c/MC_12345
Headers:
  Sforce-Duplicate-Rule-Header: allowSave=false; runAsCurrentUser=true

When you receive a DUPLICATE_VALUE error, implement this logic:

Step 1: Query existing contact by email

GET /services/data/v60.0/query/?q=SELECT Id, External_Contact_ID__c FROM Contact WHERE Email='user@example.com'

Step 2: Determine action based on result:

  • If External_Contact_ID__c matches: Update the existing record (it’s the same contact)
  • If External_Contact_ID__c differs: Log to exception queue for manual review
  • If no result: Retry upsert (transient error)

3. REST API Error Handling - Build Resilient Sync Process:

Implement a three-tier error handling strategy:

Tier 1 - Automatic Retry: For transient errors (network, locks), retry up to 3 times with exponential backoff.

Tier 2 - Duplicate Resolution: For duplicate errors, implement the query-and-decide logic above. If the email matches but external IDs differ:

if (existingContact.External_Contact_ID__c !== incomingExternalId) {
  // Log to reconciliation queue
  logDuplicateConflict({
    salesforceId: existingContact.Id,
    salesforceExternalId: existingContact.External_Contact_ID__c,
    incomingExternalId: incomingExternalId,
    email: email
  });
}

Tier 3 - Manual Review Queue: Create a custom object (Contact_Sync_Exception__c) to track unresolved conflicts for data steward review.

Additional Configuration Requirements:

API User Permissions: Ensure your integration user has:

  • “View All” and “Modify All” on Contacts
  • “Manage Duplicate Rules” if you need runtime rule bypass
  • API Enabled permission

Field-Level Considerations:

  • Make External_Contact_ID__c required and unique (external ID checkbox enabled)
  • Set Email field to not required (allows initial creation without email)
  • Create a validation rule that prevents manual external ID changes

Matching Rule Optimization: Configure your matching rule with fuzzy matching on email to catch variations:

Performance Optimization for High Volume:

With 5,000 daily syncs, implement these optimizations:

  1. Batch API Usage: Use Composite API to process up to 25 contacts per request
  2. Parallel Processing: Run multiple sync threads with different contact segments
  3. Delta Sync: Only sync contacts modified since last sync timestamp
  4. Caching: Cache external ID to Salesforce ID mappings for frequent updates

Monitoring and Reconciliation:

Implement daily reconciliation reports:

  • Contacts with duplicate emails but different external IDs
  • Upsert failures requiring manual intervention
  • External IDs in source system missing from Salesforce
  • Contacts updated in last 24 hours

Business Process Recommendation:

Define a clear policy for your organization:

  • Policy A: External ID is absolute truth - allow email changes when external ID matches
  • Policy B: Email is absolute truth - update external ID when email matches
  • Policy C: Both must match - create exception when either conflicts

Implement the chosen policy in both your duplicate rules and API error handling logic.

With this configuration, your upsert operations will succeed when legitimate, while still protecting against true duplicates. The key is making duplicate rules advisory rather than blocking for API operations, then handling conflicts intelligently in your integration code.

Check your duplicate rules in Setup. You probably have the standard ‘Standard Contact Duplicate Rule’ active, which blocks on email matches. You can modify this rule to allow updates but block creates, or create a custom rule that considers your external ID field.

Thanks for the suggestions. I’m concerned about using AllowSave because we don’t want actual duplicates in the system. The two-step query approach might work but seems inefficient for our volume. Is there a way to make the duplicate rule respect the external ID matching?

You could bypass duplicate rules entirely by setting the DuplicateRuleHeader in your REST API calls. Set allowSave to true and includeRecordDetails to false. But be careful - this completely bypasses duplicate prevention, so you need solid deduplication logic in your source system.