---
# === IDENTITY ===
id: business/erp-integration/salesforce-dynamics-365-playbook/2026
canonical_question: "How do you integrate Salesforce CRM with Microsoft Dynamics 365 Finance and Operations?"
aliases:
  - "Salesforce Dynamics 365 F&O integration playbook"
  - "How to sync Salesforce accounts and orders to D365 Finance"
  - "Salesforce to Dynamics 365 opportunity-to-order integration"
  - "Dual-write vs custom Salesforce D365 integration"
entity_type: erp_integration
domain: business > erp-integration > salesforce-dynamics-365-playbook
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Salesforce"
    vendor: "Salesforce"
    version: "API v62.0 (Spring '26)"
    edition: "Enterprise, Unlimited, Performance"
    deployment: cloud
    api_surface: "REST, SOAP, Bulk API 2.0, Platform Events, Change Data Capture"
  - name: "Microsoft Dynamics 365 Finance & Operations"
    vendor: "Microsoft"
    version: "10.0.40+ (2025 Release Wave 2 / 2026 Release Wave 1)"
    edition: "Finance, Supply Chain Management"
    deployment: cloud
    api_surface: "OData v4, Custom Services, Batch Data API (DMF), Dataverse, Business Events"

# === VERIFICATION ===
last_verified: 2026-03-07
confidence: 0.84
version: 1.0
first_published: 2026-03-07

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "D365 F&O 10.0.36 removed option to enable user-based API throttling; Salesforce v60.0 (Spring '24) deprecated legacy SOAP partner endpoints"
  next_review: 2026-09-03
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Salesforce daily API limit: 100,000 + 1,000/user license (Enterprise) or + 5,000/user license (Unlimited) per 24h rolling window"
  - "Salesforce concurrent API requests: 25 max (production); 5 max (developer/trial)"
  - "D365 F&O OData user-based throttling: 6,000 requests per 5-minute sliding window per user per web server"
  - "D365 F&O OData concurrent request limit: 52 per user per web server"
  - "D365 F&O OData combined execution time limit: 20 minutes (1,200s) within 5-minute sliding window per user"
  - "Dual Write is Microsoft-only: it connects D365 F&O to Dataverse/D365 CE -- it does NOT connect to Salesforce directly"
  - "Salesforce governor limits per transaction: 100 SOQL queries, 150 DML statements, 100 callouts, 10s CPU time (sync)"
  - "D365 F&O data entities must be public and configured for OData exposure before they can be accessed via API"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User only needs Salesforce API capabilities (no D365)"
    use_instead: "business/erp-integration/salesforce-rest-api-capabilities/2026"
  - condition: "User only needs D365 F&O API capabilities (no Salesforce)"
    use_instead: "business/erp-integration/dynamics-365-web-api-capabilities/2026"
  - condition: "User needs Salesforce-to-NetSuite integration"
    use_instead: "business/erp-integration/salesforce-netsuite-integration/2026"
  - condition: "User needs D365 CE (Sales) to D365 F&O sync (Microsoft-only stack)"
    use_instead: "business/erp-integration/dynamics-365-dual-write-capabilities/2026"
  - condition: "User needs Salesforce-to-SAP integration"
    use_instead: "business/erp-integration/salesforce-sap-integration/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "real-time sync (individual records, <1s latency)"
      - "batch/bulk (scheduled, high volume)"
      - "event-driven (Platform Events, Business Events)"
      - "file-based (CSV/XML import/export via DMF)"
  - key: data_volume
    question: "What's your daily data volume?"
    type: choice
    options:
      - "< 1,000 records/day"
      - "1,000-100,000 records/day"
      - "> 100,000 records/day"
  - key: direction
    question: "What's the data flow direction?"
    type: choice
    options:
      - "inbound (writing to D365 F&O from Salesforce)"
      - "outbound (reading from D365 F&O into Salesforce)"
      - "bidirectional sync"
  - key: middleware
    question: "Do you have an existing middleware platform?"
    type: choice
    options:
      - "Azure Integration Services (Logic Apps / Service Bus)"
      - "MuleSoft"
      - "Boomi"
      - "Power Automate + Dataverse"
      - "None (custom code)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/salesforce-dynamics-365-playbook/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-03-07)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/salesforce-rest-api-capabilities/2026"
      label: "Salesforce REST API capabilities and rate limits"
    - id: "business/erp-integration/dynamics-365-web-api-capabilities/2026"
      label: "D365 Web API capabilities and rate limits"
    - id: "business/erp-integration/dynamics-365-dataverse-api-capabilities/2026"
      label: "D365 Dataverse API capabilities"
    - id: "business/erp-integration/salesforce-oauth-authentication/2026"
      label: "Salesforce OAuth authentication flows"
  solves:
    - id: "business/erp-integration/order-to-cash-integration/2026"
      label: "Order-to-cash integration pattern"
  alternative_to:
    - id: "business/erp-integration/dynamics-365-dual-write-capabilities/2026"
      label: "D365 Dual Write -- Microsoft-only CRM-to-ERP sync (does not support Salesforce)"
  often_confused_with:
    - id: "business/erp-integration/dynamics-365-virtual-entities/2026"
      label: "D365 Virtual Entities -- read-only Dataverse exposure of F&O entities, not bidirectional sync middleware"

# === SOURCES ===
sources:
  - id: src1
    title: "Integration between finance and operations apps and third-party services"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/integration-overview
    type: official_docs
    published: 2025-10-29
    reliability: authoritative
  - id: src2
    title: "Service protection API limits - Finance & Operations"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/service-protection-api-limits
    type: official_docs
    published: 2026-01-20
    reliability: authoritative
  - id: src3
    title: "Dual-write overview - Finance & Operations"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/dual-write/dual-write-overview
    type: official_docs
    published: 2026-01-15
    reliability: authoritative
  - id: src4
    title: "Salesforce Developer Limits and Allocations Quick Reference"
    author: Salesforce
    url: https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_api.htm
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src5
    title: "How To Integrate Salesforce and Dynamics 365 Finance With No Code"
    author: ERP Software Blog
    url: https://erpsoftwareblog.com/2026/02/how-to-integrate-salesforce-and-dynamics-365-finance-with-no-code/
    type: technical_blog
    published: 2026-02-15
    reliability: moderate_high
  - id: src6
    title: "Step-by-Step Guide: Salesforce to Dynamics 365 Integration"
    author: Rapidi
    url: https://www.rapidionline.com/blog/7-steps-salesforce-dynamics-integration
    type: technical_blog
    published: 2025-06-20
    reliability: moderate_high
  - id: src7
    title: "Salesforce Microsoft Dynamics 365 Finance Integration"
    author: Rapidi
    url: https://www.rapidionline.com/salesforce-microsoft-dynamics-365-finance-integration
    type: technical_blog
    published: 2025-08-10
    reliability: moderate_high
  - id: src8
    title: "Customization guidance for dual-write"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/dual-write/custom-best-practices
    type: official_docs
    published: 2025-11-01
    reliability: authoritative
---

# Salesforce + D365 Finance & Operations Integration: Account/Order Sync, OData, Middleware

## TL;DR

- **Bottom line**: Build a custom middleware integration (Azure Logic Apps, MuleSoft, or Boomi) between Salesforce REST API and D365 F&O OData endpoints. Dual Write does NOT support Salesforce -- it only connects D365 CE (Sales) to D365 F&O. There is no Microsoft-built out-of-the-box Salesforce-to-D365-F&O connector.
- **Key limit**: D365 F&O enforces resource-based throttling (HTTP 429 with Retry-After header) and caps at 6,000 OData requests per 5-minute window per user. Salesforce adds 100,000 + per-license API calls per 24h.
- **Watch out for**: Dual Write confusion -- it is the #1 misunderstood component. It connects Dataverse/D365 Sales to D365 F&O only. Using it for Salesforce integration requires an extra Salesforce-to-Dataverse layer, doubling latency and failure points.
- **Best for**: Organizations running Salesforce as CRM and D365 F&O as ERP that need account/customer sync, opportunity-to-order handoff, price list reads, and invoice pushback.
- **Authentication**: Salesforce uses OAuth 2.0 (JWT bearer for server-to-server); D365 F&O uses Azure AD/Entra ID OAuth 2.0 client credentials flow via registered app in Azure portal.

## System Profile

This playbook covers the end-to-end integration between Salesforce (CRM, source of truth for leads, opportunities, accounts, and customer-facing data) and Microsoft Dynamics 365 Finance & Operations (ERP, source of truth for financial master data, pricing, inventory, sales orders, and invoices). It applies to D365 F&O version 10.0.38+ (cloud only -- on-premise D365 supports only the Data Management Package REST API). For organizations using D365 Business Central instead of F&O, the integration surface differs significantly (AL Extensions + API v2.0).

The architecture requires a middleware layer. Unlike the Microsoft-to-Microsoft stack (where Dual Write provides native near-real-time sync between D365 CE and D365 F&O), Salesforce-to-D365-F&O has no native connector. You must build the bridge using Azure Integration Services, MuleSoft, Boomi, or custom OData clients.

| System | Role | API Surface | Direction |
|---|---|---|---|
| Salesforce (API v62.0) | CRM -- source of truth for accounts, contacts, opportunities | REST API, Bulk API 2.0, Platform Events, CDC | Outbound (account/order push) + Inbound (pricing/invoice pull) |
| D365 F&O (10.0.40+) | ERP -- financial master, pricing, inventory, fulfillment | OData v4, Custom Services, Batch Data API (DMF), Business Events | Inbound (customer/order creation) + Outbound (pricing/invoice/inventory) |
| Middleware (Azure Logic Apps / MuleSoft / Boomi) | Integration orchestrator | Connectors for both systems | Bidirectional routing, transformation, error handling |

## API Surfaces & Capabilities

### Salesforce Side

| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| REST API | HTTPS/JSON | Individual record CRUD, <2K records | 2,000 per SOQL query | 100K+ calls/24h (edition-dependent) | Yes | No |
| Bulk API 2.0 | HTTPS/CSV | ETL, data migration, >2K records | 150M per file | 15,000 batches/24h | No | Yes |
| SOAP API | HTTPS/XML | Metadata operations, legacy | 2,000 records | Shared with REST limit | Yes | No |
| Composite API | HTTPS/JSON | Multi-object operations in one call | 25 subrequests | Counts as 1 API call | Yes | No |
| Platform Events | CometD/gRPC | Real-time event notifications | N/A | 200K-1M events/24h | Yes | N/A |
| Change Data Capture | CometD | Track field-level changes | N/A | 24h replay window | Yes | N/A |

[src4]

### D365 Finance & Operations Side

| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| OData v4 | HTTPS/JSON | Standard CRUD on data entities | Configurable via $top (default varies) | 6,000 req/5min per user per web server | Yes | Via $batch |
| Custom Services | HTTPS/JSON or SOAP | Custom business logic endpoints | Varies by implementation | Same throttling as OData | Yes | No |
| Batch Data API (DMF) | HTTPS/JSON | Large-volume async import/export | Millions (package-based) | Exempt from OData throttling | No (async) | Yes |
| Dataverse Virtual Entities | Dataverse API | Read F&O data from Power Platform | N/A | Dataverse limits apply | Yes | No |
| Business Events | Azure Service Bus / Event Grid | Real-time event notifications from F&O | N/A | Per-event processing | Yes | N/A |

[src1, src2]

## Rate Limits & Quotas

### Salesforce Per-Request Limits

| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per SOQL query | 2,000 | REST/SOAP API | Use queryMore/nextRecordsUrl for pagination |
| Max request body size | 50 MB | REST API | |
| Max composite subrequests | 25 | Composite API | All-or-nothing by default |
| Max batch file size (Bulk API 2.0) | 150 MB | Bulk API 2.0 | Split larger files |
| Max records per batch | 10,000 | Bulk API 2.0 | |
| API request timeout | 120,000 ms (2 min) | REST/SOAP sync | Bulk API polling-based |

[src4]

### Salesforce Rolling / Daily Limits

| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| Total API calls | 100,000 base | 24h rolling | Enterprise: +1,000/license; Unlimited: +5,000/license; Developer: 15,000 total |
| Concurrent API requests | 25 | Per org | Developer/Trial: 5 |
| Bulk API batches | 15,000 | 24h rolling | Shared across editions |
| Streaming API events | 200,000-1,000,000 | 24h | Enterprise: 200K; Unlimited/Performance: 1M |

[src4]

### Salesforce Governor Limits (Per Transaction)

| Limit Type | Per-Transaction Value | Notes |
|---|---|---|
| SOQL queries | 100 | Includes queries from triggers -- cascading triggers consume from same pool |
| DML statements | 150 | Each insert/update/delete counts as 1, regardless of record count |
| Callouts (HTTP requests) | 100 | External HTTP requests within a single transaction |
| CPU time | 10,000 ms (sync) / 60,000 ms (async) | Exceeded = transaction abort |
| Heap size | 6 MB (sync) / 12 MB (async) | |

[src4]

### D365 F&O Service Protection Limits

| Limit Type | Value | Window | Notes |
|---|---|---|---|
| Number of requests | 6,000 | 5-minute sliding window | Per user, per application ID, per web server |
| Combined execution time | 1,200 seconds (20 min) | 5-minute sliding window | Per user, per web server |
| Concurrent requests | 52 | Per user | Per web server instance |
| Resource-based throttling | Dynamic | CPU/memory-based | 429 response when server resources exceed threshold |
| Retry-After header | Included in 429 response | Varies | Client must honor Retry-After interval |

[src2]

**Important**: D365 F&O exempts several integration paths from OData throttling: Data Import/Export Framework (DIXF/DMF), Recurring Integrations, Data Integrator, and Virtual Tables (when Power Platform integration is enabled). For high-volume batch loads, use the Batch Data API instead of OData to avoid throttling entirely. [src2]

## Authentication

### Salesforce Authentication

| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 JWT Bearer | Server-to-server integration (recommended) | Session timeout (default 2h) | New JWT per request | Requires connected app with digital certificate |
| OAuth 2.0 Client Credentials | Server-to-server (simpler, newer) | 2h | No (new token per request) | Spring '23+; no user context |
| OAuth 2.0 Web Server Flow | User-context operations | Access: 2h; Refresh: until revoked | Yes | Requires callback URL; supports MFA |

[src4]

### D365 F&O Authentication

| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| Azure AD/Entra ID OAuth 2.0 Client Credentials | Server-to-server integration (recommended) | Default 1h (configurable) | Yes | Register app in Azure portal; assign D365 F&O environment URL as audience |
| Azure AD OAuth 2.0 Authorization Code | User-context interactive flows | Access: 1h; Refresh: 90 days | Yes | Requires redirect URI |
| S2S via Entra ID Managed Identity | Azure-hosted integrations | Auto-managed | Automatic | Only for Azure-hosted workloads (Logic Apps, Functions, AKS) |

[src1]

### Authentication Gotchas

- **D365 F&O OAuth audience URI must match exactly**: The resource/audience in the token request must be `https://your-environment.operations.dynamics.com` (no trailing slash). A mismatch returns HTTP 401 with no useful error message. [src1]
- **Salesforce JWT bearer flow requires pre-authorized user**: The integration user must be pre-authorized in the Connected App settings. If the user has IP restrictions or session policies, the JWT grant silently fails. [src4]
- **D365 F&O token caching is critical**: Unlike Salesforce (where each JWT generates a fresh token), D365 F&O tokens from Entra ID should be cached for their full lifetime (typically 1h). Requesting a new token per API call wastes quota and can trigger Entra ID throttling. [src1]
- **Salesforce Connected App callback URL**: Even for JWT bearer flow, the Connected App must have a callback URL configured (can be a dummy `https://localhost`). [src4]

## Constraints
<!-- Agents: read this section before recommending any integration approach from this unit.
     These are hard constraints that must not be violated. -->

- **Dual Write does NOT support Salesforce**: Dual Write is exclusively for D365 Customer Engagement (Sales, Marketing, Service) to D365 F&O. There is no Dual Write adapter for Salesforce. Any architecture that assumes Dual Write connects Salesforce to D365 F&O is fundamentally wrong. [src3]
- **D365 F&O OData is synchronous**: When you call OData to insert/update records, business logic runs immediately as part of the call. For high-volume inserts, this means each record triggers all validation, workflows, and posting logic synchronously. Use Batch Data API (DMF) for volumes >2,000 records. [src1]
- **D365 F&O data entities must be explicitly enabled for OData**: Not all entities are OData-enabled by default. Check `Data management > Data entities > Is public = Yes` and `Is OData accessible = Yes`. Missing this causes 404 errors with no helpful message. [src1]
- **Salesforce external IDs are essential for upserts**: Without an External ID field on Salesforce objects (to store the D365 F&O RecId or customer number), you cannot do upserts. You must create custom External ID fields before building the integration. [src4]
- **D365 F&O number sequences can cause conflicts**: If customer numbers or order numbers are auto-generated by D365 F&O, your integration must either let D365 assign numbers (and write them back to Salesforce) or configure a dedicated number sequence range for integration-created records. [src1]
- **Resource-based throttling is non-negotiable**: Even if your per-user limits are fine, D365 F&O will throttle if aggregate server CPU/memory utilization is high. This can happen during month-end processing or other peak periods. Your integration must handle 429s gracefully. [src2]
- **Salesforce field-level security blocks API access silently**: If the integration user's profile doesn't have field-level read/write access, the API returns records with those fields missing (not an error). This causes silent data loss in the integration. [src4]

## Integration Pattern Decision Tree

```
START -- Salesforce <-> D365 F&O Integration
|
+-- What's the primary process?
|   |
|   +-- Account/Customer master sync
|   |   +-- Volume < 1,000/day? --> OData real-time (individual upserts)
|   |   +-- Volume > 1,000/day? --> Batch Data API (DMF) on schedule
|   |   +-- Direction: Salesforce = master for sales-facing fields, D365 = master for financial fields
|   |   +-- ALWAYS assign External ID fields on both sides FIRST
|   |
|   +-- Opportunity-to-Order (Quote-to-Cash)
|   |   +-- Event-driven: Salesforce Platform Event on Opportunity Close --> Middleware --> D365 OData Sales Order creation
|   |   +-- Real-time (sub-minute): recommended for order accuracy
|   |   +-- Fallback: scheduled polling of closed-won Opportunities (not recommended -- stale data risk)
|   |
|   +-- Price List / Product sync (D365 --> Salesforce)
|   |   +-- Scheduled batch: D365 OData query --> transform --> Salesforce Bulk API 2.0 upsert
|   |   +-- Frequency: nightly or on price change (D365 Business Event trigger)
|   |
|   +-- Invoice pushback (D365 --> Salesforce)
|       +-- Scheduled batch: D365 OData query on posted invoices --> Salesforce REST upsert to custom Invoice object
|       +-- Or event-driven: D365 Business Event on invoice posting --> middleware --> Salesforce
|
+-- Which middleware?
|   +-- Azure-native shop --> Azure Logic Apps + Service Bus + Event Grid
|   +-- Salesforce-native shop --> MuleSoft (Salesforce-owned, pre-built D365 connector)
|   +-- Multi-cloud / existing iPaaS --> Boomi, Workato, Celigo
|   +-- Budget-constrained --> Power Automate + Dataverse bridge (limited throughput)
|
+-- Error tolerance?
    +-- Zero-loss (financial data) --> Dead letter queue + idempotent retries + reconciliation job
    +-- Best-effort (reporting data) --> Retry 3x with exponential backoff, then log and skip
```

## Quick Reference

### Process Flow Table

| Step | Source System | Trigger | Target System | Data Objects | Failure Handling |
|---|---|---|---|---|---|
| 1. Account/Customer sync | Salesforce | Account created/updated | D365 F&O | CustCustomerV3 entity | Retry 3x, then dead letter queue |
| 2. Contact sync | Salesforce | Contact created/updated | D365 F&O | smmContactPersonV2 entity | Retry 3x, skip on duplicate key |
| 3. Opportunity-to-Order | Salesforce | Opportunity Stage = Closed Won | D365 F&O | SalesOrderHeaderV2 + SalesOrderLine entities | Retry 3x, then manual review queue |
| 4. Product/Price sync | D365 F&O | Scheduled (nightly) or Business Event | Salesforce | Product2 + PricebookEntry | Full refresh with upsert on External ID |
| 5. Inventory check | D365 F&O | On-demand (Salesforce callout) | Salesforce | Custom object or Opportunity field | Real-time OData read; cache 15min |
| 6. Invoice pushback | D365 F&O | Invoice posted (Business Event) | Salesforce | Custom Invoice__c object | Retry 3x, then reconciliation batch |
| 7. Payment status sync | D365 F&O | Scheduled (hourly) | Salesforce | Custom field on Account | Upsert via External ID |

## Step-by-Step Integration Guide

### 1. Register applications and configure authentication

Set up OAuth credentials on both sides before building any data flows. [src1, src4]

**Salesforce side**: Create a Connected App in Setup > App Manager > New Connected App. Enable OAuth settings. For server-to-server, upload an X.509 certificate and select the JWT bearer flow. Note the Consumer Key.

**D365 F&O side**: Register an application in Azure Portal > Entra ID > App Registrations. Add a client secret. Under API Permissions, add Dynamics ERP (or the specific environment URL). In D365 F&O, go to System Administration > Azure Active Directory Applications and add the app's Client ID with a system user.

```bash
# Test Salesforce JWT auth (replace values)
curl -X POST https://login.salesforce.com/services/oauth2/token \
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  -d "assertion=${SALESFORCE_JWT_TOKEN}"

# Test D365 F&O auth
curl -X POST "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}" \
  -d "scope=https://${D365_ENVIRONMENT}.operations.dynamics.com/.default" \
  -d "grant_type=client_credentials"
```

**Verify**: Both calls return `{"access_token": "..."}`. Salesforce also returns `instance_url`. D365 F&O token includes `aud` matching your environment URL.

### 2. Create External ID fields and data entity mappings

Before syncing any data, create External ID fields on both systems to enable idempotent upserts. [src6]

**Salesforce**: Add a custom field `D365_Customer_Number__c` (External ID, Unique, Text 20) on Account. Add `D365_Order_Number__c` on a custom Order object or standard Opportunity.

**D365 F&O**: Use the `CustomerAccount` field as the natural key. For Salesforce-originated records, create a custom field on CustCustomerV3 entity to store the Salesforce Account ID (18-char). This requires an extension to the entity and underlying table.

**Verify**: Query the field via API:
```bash
# Salesforce: verify field exists
curl -H "Authorization: Bearer ${SF_TOKEN}" \
  "${INSTANCE_URL}/services/data/v62.0/sobjects/Account/describe" \
  | jq '.fields[] | select(.name == "D365_Customer_Number__c")'

# D365 F&O: verify entity is OData-accessible
curl -H "Authorization: Bearer ${D365_TOKEN}" \
  "https://${D365_ENV}.operations.dynamics.com/data/CustomersV3?\$top=1"
```

### 3. Build Account/Customer bidirectional sync

The most critical and foundational sync. Salesforce Account is the sales-facing master; D365 F&O Customer is the financial master. [src5, src6]

**Direction 1: Salesforce --> D365 F&O** (new account creation)
- Trigger: Salesforce Platform Event or CDC on Account create/update
- Middleware: Map Account fields to CustCustomerV3 entity
- D365 OData: POST to `/data/CustomersV3` (create) or PATCH to `/data/CustomersV3(CustomerAccount='CUST0001',dataAreaId='usmf')` (update)

**Direction 2: D365 F&O --> Salesforce** (financial data enrichment)
- Trigger: D365 Business Event on customer credit limit change or payment terms update
- Middleware: Map D365 fields to Salesforce Account
- Salesforce REST: PATCH to `/services/data/v62.0/sobjects/Account/D365_Customer_Number__c/CUST0001` (upsert via External ID)

**Verify**: Create a test account in Salesforce and verify it appears in D365 F&O within expected latency (sub-minute for real-time, within batch window for scheduled).

### 4. Implement Opportunity-to-Order handoff

The core revenue flow: Salesforce opportunity closure triggers D365 F&O sales order creation. [src5, src7]

```python
# Middleware logic (pseudo-code for Azure Function / MuleSoft flow)
# Input:  Salesforce Opportunity (Closed Won) with line items
# Output: D365 F&O Sales Order (SalesOrderHeaderV2 + SalesOrderLine)

def opportunity_to_order(sf_opportunity):
    # Step 1: Map header
    d365_header = {
        "SalesOrderNumber": "",  # Let D365 assign via number sequence
        "CustAccount": sf_opportunity["Account"]["D365_Customer_Number__c"],
        "dataAreaId": "usmf",
        "RequestedShippingDate": sf_opportunity["CloseDate"],
        "SalesOrderName": sf_opportunity["Name"][:60]
    }

    # Step 2: Create header via OData
    header_response = d365_odata_post("/data/SalesOrderHeadersV2", d365_header)
    order_number = header_response["SalesOrderNumber"]

    # Step 3: Map and create lines
    for item in sf_opportunity["OpportunityLineItems"]:
        d365_line = {
            "SalesOrderNumber": order_number,
            "dataAreaId": "usmf",
            "ItemNumber": item["Product2"]["ProductCode"],
            "SalesQuantity": item["Quantity"],
            "SalesPrice": item["UnitPrice"]
        }
        d365_odata_post("/data/SalesOrderLines", d365_line)

    # Step 4: Write order number back to Salesforce
    sf_upsert(f"Opportunity/{sf_opportunity['Id']}",
              {"D365_Order_Number__c": order_number})

    return order_number
```

**Verify**: Close an Opportunity in Salesforce sandbox. Confirm the Sales Order appears in D365 F&O within expected SLA. Verify line items match.

### 5. Configure Price List and Product sync (D365 --> Salesforce)

Products and pricing are mastered in D365 F&O and pushed to Salesforce for sales team visibility. [src6, src7]

- **Schedule**: Nightly batch or triggered by D365 Business Event on price change
- **Source**: D365 OData query on `ReleasedProductsV2` or `SalesItemPricesV2` entities
- **Target**: Salesforce `Product2` object + `PricebookEntry` object
- **Method**: Salesforce Bulk API 2.0 upsert (for volumes >200 products) or REST API upsert (for incremental changes)

```bash
# D365: query released products
curl -H "Authorization: Bearer ${D365_TOKEN}" \
  "https://${D365_ENV}.operations.dynamics.com/data/ReleasedProductsV2?\$select=ItemNumber,ProductName,SearchName&\$filter=dataAreaId eq 'usmf'&\$top=1000"

# Salesforce: upsert product via External ID
curl -X PATCH -H "Authorization: Bearer ${SF_TOKEN}" \
  -H "Content-Type: application/json" \
  "${INSTANCE_URL}/services/data/v62.0/sobjects/Product2/D365_Item_Number__c/ITEM001" \
  -d '{"Name": "Widget A", "ProductCode": "ITEM001", "IsActive": true}'
```

**Verify**: Compare product count in D365 F&O vs Salesforce after sync. Spot-check 5 random product prices.

### 6. Build Invoice pushback (D365 --> Salesforce)

After orders are fulfilled and invoiced in D365, push invoice data back to Salesforce so sales reps see payment status. [src5, src7]

- **Trigger**: D365 Business Event on `SalesInvoicePostedBusinessEvent` or scheduled OData poll
- **Source**: D365 OData entity `CustInvoiceJour` (invoice headers) + `CustInvoiceTrans` (invoice lines)
- **Target**: Custom `Invoice__c` object in Salesforce (create if it doesn't exist)
- **Key fields**: Invoice number, amount, currency, due date, payment status, linked Account (via D365 Customer Number External ID)

**Verify**: Post a test invoice in D365 F&O. Confirm the Invoice__c record appears on the corresponding Account in Salesforce.

### 7. Implement error handling and retry logic

Production integrations must handle failures gracefully. [src2]

```javascript
// Node.js: D365 F&O OData call with retry on 429
// Input:  OData endpoint, payload, auth token
// Output: API response or throws after max retries

const axios = require('axios'); // v1.6+

async function d365ODataRequest(method, url, data, token, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await axios({
        method,
        url,
        data,
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json',
          'OData-MaxVersion': '4.0',
          'OData-Version': '4.0'
        },
        timeout: 30000
      });
      return response.data;
    } catch (error) {
      if (error.response?.status === 429) {
        const retryAfter = parseInt(error.response.headers['retry-after'] || '30');
        const backoff = retryAfter * 1000 * Math.pow(1.5, attempt);
        console.warn(`429 throttled. Retry after ${backoff}ms (attempt ${attempt + 1})`);
        await new Promise(resolve => setTimeout(resolve, backoff));
        continue;
      }
      if (error.response?.status >= 500 && attempt < maxRetries - 1) {
        const backoff = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, backoff));
        continue;
      }
      throw error;
    }
  }
  throw new Error(`Max retries (${maxRetries}) exceeded for ${url}`);
}
```

**Verify**: Simulate a 429 by sending rapid OData requests. Confirm retry logic pauses and resumes successfully.

## Code Examples

### Python: Bidirectional Account/Customer Sync

```python
# Input:  Salesforce Account ID or D365 Customer Number
# Output: Synced record on the target system

import requests  # v2.31+
import time

class SfD365Sync:
    def __init__(self, sf_token, sf_instance, d365_token, d365_env):
        self.sf_headers = {"Authorization": f"Bearer {sf_token}",
                           "Content-Type": "application/json"}
        self.sf_base = f"{sf_instance}/services/data/v62.0"
        self.d365_headers = {"Authorization": f"Bearer {d365_token}",
                             "Content-Type": "application/json",
                             "OData-MaxVersion": "4.0", "OData-Version": "4.0"}
        self.d365_base = f"https://{d365_env}.operations.dynamics.com/data"

    def sf_account_to_d365(self, account_id):
        """Push Salesforce Account to D365 F&O CustCustomerV3"""
        # Fetch from Salesforce
        acct = requests.get(
            f"{self.sf_base}/sobjects/Account/{account_id}",
            headers=self.sf_headers
        ).json()

        # Map fields
        d365_customer = {
            "CustomerAccount": acct.get("D365_Customer_Number__c") or "",
            "OrganizationName": acct["Name"][:100],
            "CustomerGroupId": "10",  # Default customer group
            "dataAreaId": "usmf",
            "SalesCurrencyCode": acct.get("CurrencyIsoCode", "USD")
        }

        if d365_customer["CustomerAccount"]:
            # Update existing
            key = f"CustomerAccount='{d365_customer['CustomerAccount']}',dataAreaId='usmf'"
            resp = requests.patch(
                f"{self.d365_base}/CustomersV3({key})",
                json=d365_customer, headers=self.d365_headers
            )
        else:
            # Create new -- let D365 assign number
            resp = requests.post(
                f"{self.d365_base}/CustomersV3",
                json=d365_customer, headers=self.d365_headers
            )
            # Write back the assigned customer number to Salesforce
            new_number = resp.json()["CustomerAccount"]
            requests.patch(
                f"{self.sf_base}/sobjects/Account/{account_id}",
                json={"D365_Customer_Number__c": new_number},
                headers=self.sf_headers
            )
        return resp.json()
```

### cURL: Quick API Tests

```bash
# Input:  Valid OAuth tokens for both systems
# Output: API responses confirming connectivity

# 1. Check Salesforce API limits remaining
curl -H "Authorization: Bearer ${SF_TOKEN}" \
  "${INSTANCE_URL}/services/data/v62.0/limits" \
  | jq '{DailyApiRequests, ConcurrentPerOrgDBConnections}'

# 2. Check D365 F&O OData connectivity
curl -H "Authorization: Bearer ${D365_TOKEN}" \
  "https://${D365_ENV}.operations.dynamics.com/data/CustomersV3?\$top=1&\$select=CustomerAccount,OrganizationName"

# 3. List available D365 F&O data entities
curl -H "Authorization: Bearer ${D365_TOKEN}" \
  "https://${D365_ENV}.operations.dynamics.com/data/\$metadata" \
  -o d365_metadata.xml

# 4. Salesforce: upsert Account via External ID
curl -X PATCH -H "Authorization: Bearer ${SF_TOKEN}" \
  -H "Content-Type: application/json" \
  "${INSTANCE_URL}/services/data/v62.0/sobjects/Account/D365_Customer_Number__c/CUST-0001" \
  -d '{"Name": "Acme Corp", "BillingCity": "New York"}'
```

## Data Mapping

### Field Mapping Reference

| Source (Salesforce) | Target (D365 F&O) | Entity/Field | Type | Transform | Gotcha |
|---|---|---|---|---|---|
| Account.Name | CustomersV3.OrganizationName | CustCustomerV3 | String | Truncate to 100 chars | D365 max 100 vs SF max 255 |
| Account.BillingStreet | CustomersV3.AddressStreet | CustCustomerV3 | String | Direct | D365 splits into Street + StreetNumber; SF is single field |
| Account.BillingCity | CustomersV3.AddressCity | CustCustomerV3 | String | Direct | |
| Account.BillingState | CustomersV3.AddressState | CustCustomerV3 | String | Map SF state code to D365 state ID | D365 uses state/province ID linked to country |
| Account.BillingCountry | CustomersV3.AddressCountryRegionId | CustCustomerV3 | String | Map SF country name to ISO 3166-1 alpha-2 | D365 expects ISO code (e.g., "US"), SF may store full name |
| Account.Phone | CustomersV3.PrimaryContactPhone | CustCustomerV3 | String | Direct | Max length differs |
| Opportunity.Amount | SalesOrderHeadersV2.SalesOrderAmount | SalesOrderHeaderV2 | Currency | Convert currency if multi-currency enabled | Exchange rate timing: use SF rate or D365 rate? Define upfront |
| OpportunityLineItem.Quantity | SalesOrderLines.SalesQuantity | SalesOrderLine | Decimal | Direct | SF allows 8 decimal places; D365 default is 2. Configure D365 unit decimal precision |
| OpportunityLineItem.UnitPrice | SalesOrderLines.SalesPrice | SalesOrderLine | Currency | Direct | Tax-inclusive vs tax-exclusive: SF typically tax-exclusive; D365 depends on tax group config |
| Product2.ProductCode | SalesOrderLines.ItemNumber | SalesOrderLine | String | Direct | Must match exactly; case-sensitive in D365 |
| Account.Id (18-char) | Custom field on CustTable | Extension | String | Direct | Salesforce IDs are case-sensitive in 15-char, case-insensitive in 18-char. Always use 18-char. |

### Data Type Gotchas

- **DateTime timezone handling**: Salesforce stores all DateTime fields in UTC. D365 F&O stores dates in UTC internally but displays in user's preferred timezone. The OData API returns UTC. Match them directly -- but watch for date-only fields (Salesforce Date type has no time component; D365 DateTimeOffset always has time). [src4]
- **Currency decimal precision**: Salesforce stores currency in the org's default precision (typically 2 decimal places). D365 F&O supports up to 6 decimal places on amounts and 10 on unit prices. Map precision explicitly or rounding errors will accumulate on high-volume invoices. [src1]
- **Multi-select picklists**: Salesforce serializes multi-select picklist values as semicolon-delimited strings via API. D365 F&O uses enum values (integer-backed). You must maintain a mapping table for each multi-select field. [src4]
- **Address composite fields**: Salesforce stores billing/shipping address as separate fields (Street, City, State, PostalCode, Country). D365 F&O uses a logistics postal address entity (LogisticsPostalAddress) with country-region codes. The mapping requires a country name-to-ISO-code lookup. [src1]
- **dataAreaId (legal entity)**: Every D365 F&O data entity record requires a `dataAreaId` (company code, e.g., "usmf"). Salesforce has no equivalent concept. Your middleware must inject the correct legal entity based on business rules (e.g., Salesforce business unit or record type maps to D365 company). [src1]

## Error Handling & Failure Points

### Common Error Codes

| Source | Code | Meaning | Cause | Resolution |
|---|---|---|---|---|
| D365 F&O | 429 | Too Many Requests | OData throttling (user-based or resource-based) | Honor Retry-After header; implement exponential backoff. For persistent 429s, switch to Batch Data API (DMF) |
| D365 F&O | 400 | Bad Request | Invalid data entity payload, missing required fields, or data validation failure | Check error response body -- D365 returns detailed validation messages. Common: missing dataAreaId |
| D365 F&O | 404 | Not Found | Data entity not OData-enabled, or wrong entity name | Verify entity is public + OData-accessible in Data Management workspace |
| D365 F&O | 401 | Unauthorized | Token expired, wrong audience URI, or app not registered in D365 | Verify token audience matches environment URL exactly (no trailing slash) |
| Salesforce | INVALID_FIELD | Field doesn't exist or isn't writable | Wrong API version or missing field-level security | Check field-level security on integration user's profile; verify API version |
| Salesforce | ENTITY_IS_LOCKED | Record locked by approval process | Record is in an approval workflow | Wait for approval to complete or use system admin context |
| Salesforce | DUPLICATE_VALUE | External ID value already exists | Duplicate record on upsert | Investigate: is this a retry (safe) or a legitimate duplicate? |
| Salesforce | REQUEST_LIMIT_EXCEEDED | 24h API limit reached | Too many API calls | Reduce polling frequency; switch to event-driven architecture; request limit increase |

[src2, src4]

### Failure Points in Production

- **D365 F&O number sequence exhaustion**: Auto-generated customer or order numbers can run out if the number sequence range is too small for integration volume. Fix: `Configure a dedicated number sequence range for integration-created records with sufficient capacity (e.g., 10M range).` [src1]
- **Salesforce Platform Event replay failure**: Platform Events have a 72h retention window (previously 24h). If your middleware is down for longer, events are permanently lost. Fix: `Implement a daily reconciliation batch job that queries both systems and detects/fixes drift. Never rely solely on events for data integrity.` [src4]
- **D365 F&O Business Event delivery failures**: Business Events use Azure Service Bus or Event Grid as transport. If the endpoint is unreachable, events queue up to the retention limit and then drop. Fix: `Use Azure Service Bus with dead-letter queue enabled. Monitor dead-letter depth with Azure Monitor alerts.` [src1]
- **Multi-company routing errors**: If your middleware sends a record to the wrong D365 legal entity (dataAreaId), the data lands in the wrong company. This is an accounting error that may not be caught until month-end reconciliation. Fix: `Validate dataAreaId mapping in middleware with a lookup table. Add a validation step that rejects records with unmapped business units.` [src1]
- **OAuth token expiration during long-running batches**: A batch sync that takes >1h will hit D365 F&O token expiration (default 1h). Fix: `Implement proactive token refresh: check token expiry before each API call, refresh when <5 minutes remain.` [src1]
- **Salesforce field-level security silent data loss**: If the integration user's profile lacks read access to a field, the API simply omits that field from the response -- no error. Fix: `Create a dedicated integration user with a custom profile that has explicit read/write access to ALL integration-relevant fields. Audit with the /describe endpoint.` [src4]

## Anti-Patterns

### Wrong: Using Dual Write as a Salesforce-to-D365-F&O bridge

```
// WRONG -- Dual Write only connects D365 CE (Sales/Marketing/Service) to D365 F&O
// Architecture: Salesforce --> Dual Write --> D365 F&O
// This does not work. Dual Write has no Salesforce adapter.

// Some architects propose: Salesforce --> Dataverse --> Dual Write --> D365 F&O
// This adds an unnecessary Dataverse hop, doubles latency, creates two
// sync failure points, and requires maintaining Salesforce-to-Dataverse
// sync separately. It's complexity without benefit.
```

### Correct: Direct middleware integration

```
// CORRECT -- Salesforce REST API --> Middleware --> D365 F&O OData
// Single integration layer, direct API-to-API mapping
// Architecture:
//   Salesforce Platform Event --> Azure Logic App --> D365 OData POST
//   D365 Business Event --> Azure Service Bus --> Logic App --> SF REST PATCH

// Benefits:
// - Single failure point (middleware only)
// - Direct field mapping without Dataverse intermediary
// - Full control over transformation logic
// - Cheaper (no Dataverse license required)
```

[src3]

### Wrong: Polling Salesforce for changes on a timer

```python
# WRONG -- Polling every 5 minutes burns API calls and misses changes between polls
import schedule

def poll_salesforce_accounts():
    # This runs every 5 min = 288 times/day PER object
    # 10 objects = 2,880 API calls/day just for polling
    # Plus each poll queries ALL records modified since last poll
    accounts = sf_query("SELECT Id, Name FROM Account WHERE LastModifiedDate > :last_run")
    for acct in accounts:
        sync_to_d365(acct)

schedule.every(5).minutes.do(poll_salesforce_accounts)
```

### Correct: Event-driven with Platform Events or CDC

```python
# CORRECT -- Subscribe to Salesforce Change Data Capture
# Zero API calls wasted on polling; instant notification on change

from aiosfstream import SalesforceStreamingClient  # v0.5+

async def listen_for_changes():
    async with SalesforceStreamingClient(
        consumer_key=SF_CONSUMER_KEY,
        consumer_secret=SF_CONSUMER_SECRET,
        username=SF_USERNAME,
        password=SF_PASSWORD
    ) as client:
        await client.subscribe("/data/AccountChangeEvent")
        async for message in client:
            change = message["data"]["payload"]
            header = change["ChangeEventHeader"]
            if header["changeType"] in ("CREATE", "UPDATE"):
                sync_to_d365(change)
```

### Wrong: Sending records one-by-one to D365 F&O for bulk loads

```python
# WRONG -- 10,000 individual OData calls for a nightly product sync
for product in all_products:  # 10,000 products
    d365_odata_post("/data/ReleasedProductsV2", product)  # 10K API calls
    # Burns through 6,000-request-per-5-min limit in under 3 minutes
    # Triggers 429 throttling, takes hours to complete
```

### Correct: Use Batch Data API (DMF) for bulk loads

```python
# CORRECT -- Package data into a DMF import file, submit as single async job
import requests
import json

def bulk_import_to_d365(records, entity_name, d365_token, d365_env):
    """Use Data Management Framework for bulk import (exempt from OData throttling)"""
    # Step 1: Create a data package (CSV or JSON)
    csv_content = convert_to_csv(records)

    # Step 2: Upload package via DMF REST API
    upload_url = f"https://{d365_env}.operations.dynamics.com/data/DataManagementDefinitionGroups/Microsoft.Dynamics.DataEntities.ImportFromPackage"
    response = requests.post(upload_url,
        headers={"Authorization": f"Bearer {d365_token}",
                 "Content-Type": "application/json"},
        json={
            "packageUrl": upload_package_to_blob(csv_content),
            "definitionGroupId": "SFProductSync",
            "executionId": generate_execution_id(),
            "execute": True,
            "overwrite": True,
            "legalEntityId": "usmf"
        })

    # Step 3: Poll for completion
    execution_id = response.json()["value"]
    return poll_dmf_status(execution_id, d365_token, d365_env)
```

[src1, src2]

## Common Pitfalls

- **Pitfall 1 -- Assuming Dual Write works for Salesforce**: The #1 architectural mistake. Architects who come from Microsoft-only shops assume Dual Write connects any CRM to D365 F&O. It does not. It only connects D365 Customer Engagement apps (which run on Dataverse) to D365 F&O. Fix: `Use direct API integration via middleware. If you must use Dataverse, build a separate Salesforce-to-Dataverse sync first, but understand this doubles your integration surface.` [src3]
- **Pitfall 2 -- Ignoring dataAreaId (legal entity)**: Every D365 F&O OData call requires a legal entity. If your Salesforce org serves multiple companies, you must route records to the correct dataAreaId. Fix: `Build a mapping table: Salesforce Business Unit or Record Type --> D365 dataAreaId. Validate before every write.` [src1]
- **Pitfall 3 -- Not handling D365 F&O data entity validation errors**: D365 F&O runs full business validation on OData inserts (number sequences, mandatory fields, reference data). A 400 error response contains a detailed message, but many middleware implementations swallow it. Fix: `Parse the error response body (JSON), extract the InnerError.message field, log it with the source record ID, and route to a manual review queue.` [src1]
- **Pitfall 4 -- Building the integration without External ID fields**: Upserts require External ID fields. Without them, every sync creates duplicates or fails on lookup. Fix: `Define External ID strategy BEFORE writing any integration code. Minimum: Salesforce Account.D365_Customer_Number__c and D365 CustTable custom Salesforce ID field.` [src6]
- **Pitfall 5 -- Overestimating D365 F&O OData throughput**: OData is synchronous -- each call triggers full business logic. A simple SalesOrderHeadersV2 POST can take 500ms-2s depending on validation complexity. At 6,000 requests per 5-min limit, you get ~1,200 requests/minute theoretical max, but effective throughput is lower due to execution time limits. Fix: `For >500 records/batch, use DMF. For real-time, keep to individual record operations only.` [src1, src2]
- **Pitfall 6 -- Not testing with production data volumes in sandbox**: D365 F&O sandbox environments may have fewer web servers than production, meaning lower aggregate throttling capacity. Salesforce sandbox has lower API limits. Fix: `Load-test against a full-copy sandbox with production data volumes. Monitor for 429 responses and measure actual throughput.` [src2]

## Diagnostic Commands

```bash
# === Salesforce Diagnostics ===

# Check remaining API limits
curl -H "Authorization: Bearer ${SF_TOKEN}" \
  "${INSTANCE_URL}/services/data/v62.0/limits" \
  | jq '{DailyApiRequests: .DailyApiRequests, ConcurrentPerOrgDBConnections: .ConcurrentPerOrgDBConnections}'

# Verify integration user permissions
curl -H "Authorization: Bearer ${SF_TOKEN}" \
  "${INSTANCE_URL}/services/data/v62.0/sobjects/Account/describe" \
  | jq '[.fields[] | select(.name | test("D365|Billing|Phone")) | {name, type, updateable, createable}]'

# Test CDC subscription
curl -H "Authorization: Bearer ${SF_TOKEN}" \
  "${INSTANCE_URL}/services/data/v62.0/sobjects/AccountChangeEvent/describe"

# === D365 F&O Diagnostics ===

# Test OData connectivity and list entities
curl -H "Authorization: Bearer ${D365_TOKEN}" \
  "https://${D365_ENV}.operations.dynamics.com/data/" \
  | head -50

# Check specific entity availability
curl -H "Authorization: Bearer ${D365_TOKEN}" \
  "https://${D365_ENV}.operations.dynamics.com/data/CustomersV3?\$top=1&\$count=true"

# Check D365 F&O data entity metadata
curl -H "Authorization: Bearer ${D365_TOKEN}" \
  "https://${D365_ENV}.operations.dynamics.com/data/CustomersV3?\$top=0" \
  -D -

# Verify Business Events endpoint registration
# (via D365 F&O UI: System Administration > Business Events > Business Events Catalog)
# No direct API diagnostic -- check Azure Service Bus dead-letter queue depth:
az servicebus queue show --resource-group ${RG} --namespace-name ${SB_NS} \
  --name ${QUEUE_NAME} --query "countDetails.deadLetterMessageCount"
```

## Version History & Compatibility

### Salesforce API Versions

| API Version | Release | Status | Key Changes |
|---|---|---|---|
| v62.0 | Spring '26 (Feb 2026) | Current | Latest GA |
| v61.0 | Winter '26 (Oct 2025) | Supported | Minor CDC improvements |
| v60.0 | Spring '24 (Feb 2024) | Supported | Deprecated legacy SOAP partner endpoints |
| v58.0 | Spring '23 (Feb 2023) | Supported | Added Client Credentials OAuth flow |

[src4]

### D365 F&O Versions

| Version | Release | Status | Key Changes |
|---|---|---|---|
| 10.0.41 | 2026 Wave 1 (Apr 2026) | Upcoming | Enhanced Business Events framework |
| 10.0.40 | 2025 Wave 2 (Oct 2025) | Current | Virtual entity improvements |
| 10.0.39 | 2025 Wave 2 (Jul 2025) | Supported | |
| 10.0.36 | 2024 Wave 2 | Supported | User-based API limits disabled permanently; option removed |

[src1, src2]

### Deprecation Policy

- **Salesforce**: API versions are supported for a minimum of 3 years. Versions are retired in groups -- always pin to a specific version and plan migration within 2 years. [src4]
- **D365 F&O**: Microsoft follows a continuous update model with one major release wave per half-year. Data entities and OData endpoints are generally backward-compatible, but individual fields may be deprecated with one release notice. Always test against the next release in the sandbox preview environment. [src1]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Salesforce is your CRM and D365 F&O is your ERP | Both CRM and ERP are Microsoft (D365 CE + D365 F&O) | business/erp-integration/dynamics-365-dual-write-capabilities/2026 |
| You need bidirectional Account/Customer, Opportunity-to-Order, or Invoice pushback | You only need to display D365 data in Salesforce (read-only) | Salesforce Connect External Objects (OData adapter) -- simpler, no middleware |
| Daily volume is <100K records (real-time or batch) | Daily volume exceeds 1M records requiring near-real-time | Custom ETL pipeline with D365 DMF + Salesforce Bulk API 2.0 |
| You need financial data (invoices, payments) reflected in Salesforce | You only need contact/lead sync without financial data | Simple point-to-point API integration (no middleware needed) |
| You have middleware budget (Azure, MuleSoft, Boomi) | Zero middleware budget and zero dev resources | Pre-built connector (Rapidi, Celigo) -- higher per-record cost but no dev needed |

## Cross-System Comparison

| Capability | Salesforce (API v62.0) | D365 F&O (10.0.40+) | Notes |
|---|---|---|---|
| API Style | REST + SOAP + Bulk | OData v4 + Custom Services + DMF | Both REST-based; OData more structured |
| Rate Limits | 100K+ calls/24h rolling (edition-based) | 6,000/5min per user per web server + resource-based | D365 is per-window not per-day; harder to predict |
| Bulk Import | Bulk API 2.0 (150MB files, async) | DMF/DIXF (package-based, async, exempt from throttling) | Both async; D365 DMF is more complex but no rate limit |
| Event-Driven | Platform Events + CDC (72h replay) | Business Events + Data Events (Azure transport) | SF more mature; D365 requires Azure infrastructure |
| Auth Model | OAuth 2.0 (JWT, Client Credentials, Web Server) | Azure AD/Entra ID OAuth 2.0 | SF self-contained; D365 depends on Azure AD |
| Sandbox Support | Full Copy, Partial Copy, Developer | Tier-1 (dev), Tier-2+ (standard/premium sandbox) | Both have sandbox; D365 sandbox may have fewer web servers |
| Data Model | Objects + Fields (flat, flexible) | Data Entities + Tables (normalized, rigid) | SF more flexible; D365 more structured (double-entry accounting) |
| Metadata API | Describe calls (per object) | $metadata (full OData EDMX, can be very large) | D365 metadata is 100MB+ XML; parse selectively |

## Important Caveats

- **No native Microsoft connector for Salesforce-to-D365-F&O**: Unlike the D365 CE ecosystem (where Dual Write, Data Integrator, and Virtual Entities provide native integration), there is no first-party Microsoft product that connects Salesforce to D365 F&O. All integrations require custom middleware or third-party connectors. [src3]
- **D365 F&O throttling is dynamic**: Resource-based throttling activates when server CPU/memory is high -- even if your per-user limits are fine. Month-end close, batch jobs, and report generation all increase server load. Plan integration windows around these peaks. [src2]
- **Salesforce API limits vary by edition**: Developer edition has only 15,000 total API calls/24h. Sandbox limits are lower than production. Always verify limits in Setup > Company Information > API Usage. [src4]
- **D365 F&O data entities are not universally available**: Not every D365 table has a corresponding data entity. Custom tables require custom entity development (Visual Studio + LCS deployment). This can take weeks. [src1]
- **Exchange rate timing matters for multi-currency**: If Salesforce and D365 use different exchange rates, currency amounts will drift. Define which system's rate is authoritative and apply it consistently at the middleware layer.
- **Rate limits and service protection thresholds are subject to change with each release**. Always verify against current documentation before finalizing integration architecture.

## Related Units

- [Salesforce REST API Capabilities](/business/erp-integration/salesforce-rest-api-capabilities/2026)
- [D365 Web API Capabilities](/business/erp-integration/dynamics-365-web-api-capabilities/2026)
- [D365 Dataverse API Capabilities](/business/erp-integration/dynamics-365-dataverse-api-capabilities/2026)
- [D365 Dual Write Capabilities](/business/erp-integration/dynamics-365-dual-write-capabilities/2026)
- [D365 DMF Import/Export](/business/erp-integration/dynamics-365-dmf-import-export/2026)
- [Salesforce OAuth Authentication](/business/erp-integration/salesforce-oauth-authentication/2026)
- [Order-to-Cash Integration Pattern](/business/erp-integration/order-to-cash-integration/2026)
- [D365 Virtual Entities](/business/erp-integration/dynamics-365-virtual-entities/2026)
