Salesforce + SAP S/4HANA Integration: Customer/Order Sync, OData vs RFC, Middleware

Type: ERP Integration Systems: Salesforce (API v62.0) + SAP S/4HANA (2408 / Cloud 2502) Confidence: 0.88 Sources: 7 Verified: 2026-03-02 Freshness: evolving

TL;DR

System Profile

This card covers the integration between Salesforce (CRM, source of truth for leads, opportunities, and customer-facing data) and SAP S/4HANA (ERP, source of truth for financial master data, pricing, inventory, and order fulfillment). It applies to SAP S/4HANA Cloud (Public and Private Edition) and On-Premise 2021+ releases.

SystemRoleAPI SurfaceDirection
Salesforce (API v62.0)CRM — customers, leads, opportunitiesREST API, Bulk API 2.0, Platform EventsOutbound + Inbound
SAP S/4HANA (2408 / Cloud 2502)ERP — financial master, inventory, fulfillmentOData V2/V4, SOAP, RFC/BAPI, IDocInbound + Outbound
Middleware (MuleSoft / SAP CPI)Integration orchestratorConnectors for both systemsBidirectional

API Surfaces & Capabilities

Salesforce Side

API SurfaceProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
REST APIHTTPS/JSONIndividual record CRUD, <2K records2,000 per SOQL query100K+ calls/24hYesNo
Bulk API 2.0HTTPS/CSVETL, data migration, >2K records150M per file15,000 batches/24hNoYes
SOAP APIHTTPS/XMLMetadata operations, legacy2,000 recordsShared with RESTYesNo
Composite APIHTTPS/JSONMulti-object operations25 subrequests1 API callYesNo
Platform EventsCometD/gRPCReal-time event notificationsN/A200K-1M events/24hYesN/A
Change Data CaptureCometDField-level change trackingN/A24h replay windowYesN/A

SAP S/4HANA Side

API SurfaceProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
OData V2HTTPS/JSON or XMLStandard CRUD (most services)1,000/page~100 req/s (Cloud)YesVia $batch
OData V4HTTPS/JSONNewer APIs (greenfield)1,000/page~100 req/s (Cloud)YesVia $batch
SOAP (WSDL)HTTPS/XMLLegacy, WS-Security scenariosVariesFair-useYesNo
RFC/BAPISAP protocol (JCo)Custom ABAP logicSingle callPool-dependentYesNo
IDocALE/EDIBulk document transferUnlimited (async)Throughput-dep.NoYes
SAP Event MeshAMQP/MQTT/RESTReal-time pub/subN/ATier-dependentYesN/A

Rate Limits & Quotas

Salesforce Per-Request Limits

Limit TypeValueApplies ToNotes
Max records per SOQL query2,000REST/SOAP APIUse queryMore for pagination
Max request body size50 MBREST API
Max composite subrequests25Composite APIAll-or-nothing by default
Max batch file size150 MBBulk API 2.0Split larger files
Max records per batch10,000Bulk API 2.0
API request timeout600,000 msAll REST/SOAP10 minutes

Salesforce Rolling / Daily Limits

Limit TypeValueWindowEdition Differences
Total API calls100,000 base24h rollingEnterprise: +1,000/license; Unlimited: +5,000/license; Developer: 15,000 total
Concurrent API requests25Per orgDeveloper/Trial: 5
Bulk API batches15,00024h rollingShared across editions
Streaming API events200K-1M24hEnterprise: 200K; Unlimited: 1M

Salesforce Governor Limits (Per Transaction)

Limit TypePer-Transaction ValueNotes
SOQL queries100Includes queries from triggers
DML statements150Each insert/update/delete counts as 1
Callouts (HTTP)100External HTTP requests within a transaction
CPU time10,000 ms (sync) / 60,000 ms (async)Exceeded = abort
Heap size6 MB (sync) / 12 MB (async)

SAP S/4HANA Rate Limits

Limit TypeValueApplies ToNotes
Default throughput~100 req/sOData (Cloud)Managed via SAP API Management
Burst capacity~200 req/sOData (Cloud)Short-burst before throttling
Max page size1,000 records/pageOData queriesConfigurable via $top
$batch max changesets100 per requestOData $batch (Cloud)On-premise varies
HTTP 429 responseRetry-After headerAll Cloud APIsExponential backoff recommended

Authentication

Salesforce Authentication

FlowUse WhenToken LifetimeRefresh?Notes
OAuth 2.0 JWT BearerServer-to-server (recommended)2h defaultNew JWT per requestRequires connected app + certificate
OAuth 2.0 Client CredentialsServer-to-server (simpler)2hNew token per requestSpring '23+; no user context
Username-Password (legacy)Testing onlySession timeoutNoDo NOT use in production

SAP S/4HANA Authentication

FlowUse WhenToken LifetimeRefresh?Notes
OAuth 2.0 Client CredentialsS/4HANA Cloud12h defaultYesRequires Communication Arrangement
Basic Auth + CSRFOn-premiseSession-basedFetch new CSRF per sessionCSRF required for all writes
X.509 Client CertificatesHigh-security on-premCert validityN/ARecommended for production

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START — Salesforce <-> SAP S/4HANA Integration
+-- SAP protocol selection?
|   +-- S/4HANA Cloud Public? -> OData V4 (preferred) or V2
|   +-- Standard API exists on api.sap.com? -> OData
|   +-- Custom logic not exposed via OData? -> RFC/BAPI (on-prem only)
|   +-- High-volume batch (>10K records)? -> IDoc or OData $batch
+-- Salesforce protocol selection?
|   +-- Real-time <2K records? -> REST API (Composite for multi-object)
|   +-- Batch/bulk >2K records? -> Bulk API 2.0
|   +-- Change notifications FROM Salesforce? -> Platform Events or CDC
|   +-- Show SAP data without copying? -> Salesforce Connect (read-only)
+-- Middleware selection?
|   +-- Salesforce strategic? -> MuleSoft
|   +-- SAP strategic? -> SAP Integration Suite / CPI
|   +-- Neither dominant? -> Boomi, Workato, or Jitterbit
+-- Data direction?
|   +-- SF -> SAP: Middleware maps SF objects to SAP OData entities
|   +-- SAP -> SF: Middleware queries SAP OData, upserts to SF REST
|   +-- Bidirectional: Define source-of-truth per field first
+-- Error handling?
    +-- Zero-loss: idempotency keys + dead letter queue
    +-- Best-effort: retry 3x with backoff, then log and alert

Quick Reference

Key SAP OData Services

SAP OData ServiceAPI Hub IDUse CaseOData Ver.Key Entity Sets
Business PartnerAPI_BUSINESS_PARTNERCustomer master syncV2A_BusinessPartner, A_BusinessPartnerAddress
Sales OrderAPI_SALES_ORDER_SRVOrder creationV2A_SalesOrder, A_SalesOrderItem
Sales Order (V4)API_SALESORDER_0001Order creation (newer)V4SalesOrder, SalesOrderItem
Product MasterAPI_PRODUCT_SRVProduct catalog syncV2A_Product, A_ProductDescription
Material AvailabilityAPI_MATERIAL_AVAILABILITY_INFOATP stock checkV2SupplyDemandItem
Pricing ConditionsAPI_SLSPRICINGCONDITIONRECORD_SRVPricing syncV2A_SlsPrcgCndnRecdValidity
Outbound DeliveryAPI_OUTBOUND_DELIVERY_SRV_0002Fulfillment statusV2A_OutbDeliveryHeader, A_OutbDeliveryItem

Salesforce Objects for SAP Integration

Salesforce ObjectSAP EquivalentSync DirectionTypical Frequency
AccountBusiness Partner (BP)BidirectionalReal-time or 15-min batch
ContactBP ContactBidirectionalReal-time
Opportunity (Closed Won)Sales Order (VA01)SF -> SAPEvent-driven
Product2Material Master (MM01)SAP -> SFDaily batch
PricebookEntryCondition Records (VK11)SAP -> SFDaily or on-change

Step-by-Step Integration Guide

1. Set up authentication on both sides

Salesforce: Create a Connected App with OAuth 2.0 JWT Bearer flow. SAP S/4HANA Cloud: Create a Communication Arrangement. [src1, src2]

# Test Salesforce auth
curl -X POST https://login.salesforce.com/services/oauth2/token \
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  -d "assertion=${JWT_TOKEN}"

Verify: Response includes access_token and instance_url

2. Fetch SAP CSRF token and test OData connectivity

Every SAP write requires a CSRF token. Fetch with GET, cache token + cookies. [src6]

# Fetch CSRF token from SAP
curl -X GET "https://{sap-host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=1" \
  -H "Authorization: Basic ${SAP_CREDENTIALS}" \
  -H "X-CSRF-Token: Fetch" \
  -c cookies.txt -D headers.txt

Verify: Response returns HTTP 200 with x-csrf-token header (not "Required")

3. Sync customer data (Salesforce Account to SAP Business Partner)

Map Account fields to Business Partner and POST via OData. [src1, src5]

curl -X POST "https://{sap-host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner" \
  -H "X-CSRF-Token: ${CSRF_TOKEN}" -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{"BusinessPartnerCategory":"2","OrganizationBPName1":"Acme Corp"}'

Verify: HTTP 201 with BusinessPartner number in response

4. Create Sales Order from Salesforce Opportunity

On Opportunity closed-won, create Sales Order via API_SALES_ORDER_SRV. [src1]

curl -X POST "https://{sap-host}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder" \
  -H "X-CSRF-Token: ${CSRF_TOKEN}" -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{"SalesOrderType":"OR","SalesOrganization":"1000","SoldToParty":"BP_NUM","to_Item":[{"Material":"MAT001","RequestedQuantity":"5"}]}'

Verify: HTTP 201 with SalesOrder number

5. Implement delivery status sync (SAP to Salesforce)

Query SAP for changed deliveries, upsert to Salesforce custom object. [src1, src2]

# Query SAP for recent deliveries
curl "https://{sap-host}/sap/opu/odata/sap/API_OUTBOUND_DELIVERY_SRV_0002/A_OutbDeliveryHeader?\
$filter=LastChangeDateTime gt datetime'2026-03-01T00:00:00'"

# Upsert to Salesforce
curl -X PATCH "${SF_INSTANCE}/services/data/v62.0/sobjects/Delivery_Status__c/SAP_Delivery__c/${DOC}" \
  -H "Authorization: Bearer ${SF_TOKEN}" -H "Content-Type: application/json" \
  -d '{"Status__c":"Shipped"}'

Verify: Salesforce record updated with delivery status

Code Examples

Python: Bidirectional Customer Sync

# Input:  Salesforce Account changes + SAP BP API access
# Output: Synced customer records with conflict resolution

import requests

def get_sap_csrf_token(session, sap_host, auth):
    """Fetch CSRF token + cookies. Must call before any write."""
    resp = session.get(
        f"{sap_host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=1",
        headers={"X-CSRF-Token": "Fetch", "Accept": "application/json"},
        auth=auth
    )
    resp.raise_for_status()
    return resp.headers["x-csrf-token"]

def sync_account_to_bp(account, session, sap_host, auth, csrf):
    """Create SAP Business Partner from Salesforce Account."""
    payload = {
        "BusinessPartnerCategory": "2",
        "OrganizationBPName1": account["Name"][:40],
        "SearchTerm1": account["Name"][:20].upper(),
    }
    resp = session.post(
        f"{sap_host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner",
        json=payload,
        headers={"X-CSRF-Token": csrf},
        auth=auth
    )
    if resp.status_code == 429:
        raise Exception(f"Rate limited. Retry after {resp.headers.get('Retry-After', 5)}s")
    resp.raise_for_status()
    return resp.json()["d"]["BusinessPartner"]

JavaScript/Node.js: Sales Order Creation

// Input:  Salesforce Opportunity (Closed Won event)
// Output: SAP Sales Order number

const axios = require('axios'); // v1.7+

async function getSapCsrfToken(sapHost, auth) {
  const resp = await axios.get(
    `${sapHost}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder?$top=1`,
    { headers: { 'X-CSRF-Token': 'Fetch' }, auth, withCredentials: true }
  );
  return { token: resp.headers['x-csrf-token'], cookies: resp.headers['set-cookie'] };
}

async function createSalesOrder(sapHost, auth, orderData) {
  const { token, cookies } = await getSapCsrfToken(sapHost, auth);
  const resp = await axios.post(
    `${sapHost}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder`,
    { SalesOrderType: 'OR', SalesOrganization: orderData.salesOrg,
      SoldToParty: orderData.sapCustomerNumber,
      to_Item: orderData.items.map((it, i) => ({
        SalesOrderItem: String((i+1)*10).padStart(6,'0'),
        Material: it.materialNumber, RequestedQuantity: String(it.qty)
      }))
    },
    { headers: { 'X-CSRF-Token': token, 'Cookie': cookies.join('; ') }, auth }
  );
  return resp.data.d.SalesOrder;
}

cURL: Quick End-to-End Connectivity Test

# Test Salesforce API
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer ${SF_TOKEN}" "${SF_INSTANCE}/services/data/v62.0/"
# Expected: 200

# Test SAP OData + CSRF fetch
curl -s -o /dev/null -w "%{http_code}" -H "X-CSRF-Token: Fetch" \
  -u "${SAP_USER}:${SAP_PASS}" \
  "https://${SAP_HOST}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?\$top=1"
# Expected: 200

Data Mapping

Field Mapping Reference

Salesforce FieldSAP S/4HANA FieldTypeTransformGotcha
Account.NameBP.OrganizationBPName1StringTruncate to 40 charsSAP max 40; SF max 255
Account.BillingStreetBPAddress.StreetNameStringDirectSAP splits street + house number
Account.BillingCountryBPAddress.CountryStringISO 3166-1 alpha-2Must be 2-letter code
Opportunity.CloseDateSO.RequestedDeliveryDateDateV2: datetime'...'; V4: ISO 8601OData V2 uses epoch ms format
Product2.ProductCodeMaterial.MaterialStringPad to 18 chars (leading zeros)SAP IDs are 18-char padded
OpportunityLineItem.QuantitySOItem.RequestedQuantityDecimalDirectSAP returns as string in OData
OpportunityLineItem.UnitPriceSOItem (via pricing)CurrencyCannot set directlySAP pricing from condition records

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeSystemMeaningResolution
403SAPCSRF token invalid/expiredRe-fetch CSRF token with session cookies
429BothRate limit exceededExponential backoff (2^n seconds, max 5 retries)
401SAPMissing Communication ArrangementVerify Communication User + Arrangement
REQUEST_LIMIT_EXCEEDEDSalesforceDaily API limit hitCheck /limits/ endpoint; optimize or purchase add-on
UNABLE_TO_LOCK_ROWSalesforceConcurrent record updateRetry with jitter; serialize same-record updates
/IWBEP/CX_MGW_BUSI_EXCEPTIONSAPBusiness logic rejectionRead structured error message for details

Failure Points in Production

Anti-Patterns

Wrong: Polling SAP every 60 seconds for all records

// BAD — wastes API calls; misses changes between polls
setInterval(async () => {
  const all = await fetchAllCustomersFromSAP();
  for (const c of all) await upsertToSalesforce(c);
}, 60000);

Correct: Use delta query with LastChangeDateTime filter

// GOOD — fetch only changed records; minimize API calls
async function syncDelta(lastSync) {
  const filter = `LastChangeDateTime gt datetime'${lastSync}'`;
  const changes = await fetchFromSAP(`/A_BusinessPartner?$filter=${filter}`);
  const batches = chunk(changes, 25);
  for (const batch of batches) await compositeUpsert(batch);
}

Wrong: Direct SAP callouts inside Salesforce Apex triggers

// BAD — callout in trigger context; cannot do DML after
trigger OrderTrigger on Opportunity (after update) {
    Http h = new Http();
    h.send(new HttpRequest()); // Callout in trigger = BAD
}

Correct: Fire Platform Events for async processing

// GOOD — Platform Event; middleware picks it up
trigger OrderTrigger on Opportunity (after update) {
    List<Order_Created__e> events = new List<Order_Created__e>();
    // ... build events for closed-won opportunities
    EventBus.publish(events); // Async, no callout limit
}

Wrong: Hardcoding SAP Material IDs without padding

# BAD — SAP returns 404 for unpadded IDs
material_id = "MAT001"  # SAP expects '000000000000MAT001'

Correct: Always pad to 18 characters

# GOOD — pad with leading zeros
material_id = "MAT001".zfill(18)  # '000000000000MAT001'

Common Pitfalls

Diagnostic Commands

# Check Salesforce API usage
curl -s -H "Authorization: Bearer ${SF_TOKEN}" \
  "${SF_INSTANCE}/services/data/v62.0/limits/" | \
  python3 -c "import sys,json; d=json.load(sys.stdin); \
  print(f\"API remaining: {d['DailyApiRequests']['Remaining']}/{d['DailyApiRequests']['Max']}\")"

# Test SAP OData metadata
curl -s -u "${SAP_USER}:${SAP_PASS}" \
  "https://${SAP_HOST}/sap/opu/odata/sap/API_BUSINESS_PARTNER/\$metadata" | head -50

# Verify SAP Communication Arrangement
curl -s -u "${SAP_USER}:${SAP_PASS}" \
  "https://${SAP_HOST}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?\$top=1"

# Monitor SAP request performance
curl -s -o /dev/null -w "HTTP %{http_code} | Time: %{time_total}s\n" \
  -u "${SAP_USER}:${SAP_PASS}" \
  "https://${SAP_HOST}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder?\$top=10"

Version History & Compatibility

Salesforce API Versions

API VersionReleaseStatusKey Changes
v62.0Spring '26CurrentLatest composite API improvements
v61.0Winter '26SupportedEnhanced CDC replay
v60.0Spring '24SupportedDeprecated legacy SOAP partner endpoints
v58.0Spring '23SupportedIntroduced Client Credentials flow

SAP S/4HANA Releases

ReleaseDateStatusKey Changes
Cloud 2502Feb 2026CurrentNew OData V4 Sales Order API
2408 (On-Prem)Aug 2024Current LTSExpanded OData V4 catalog
Cloud 2308Aug 2023SupportedEnhanced BP OData V4
2021 FPS022022SupportedMin recommended for OData V4

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Bidirectional customer/order sync between SF CRM and SAP ERPOnly reading SAP data (no writes)Salesforce Connect (OData external objects)
Real-time order creation on opportunity closeBatch migration >100K recordsSAP Data Migration Cockpit or LT Replication Server
Event-driven delivery/fulfillment status syncSimple one-time data loadSAP Data Services or Informatica
Multi-object orchestration (account + order + items)SAP ECC (pre-S/4HANA) without ODataSAP ECC integration (RFC/IDoc approach)

Cross-System Comparison

CapabilitySalesforce (v62.0)SAP S/4HANA (2408/Cloud)Notes
API StyleREST (native) + SOAPOData V2/V4 + SOAP + RFCSAP OData has SAP-specific conventions
Rate Limits100K+/24h (hard cap)~100 req/s (configurable)SF per-day; SAP per-second
Bulk ImportBulk API 2.0 (150MB/file)OData $batch (100 changesets) or IDocSF bulk more mature
Event-DrivenPlatform Events, CDCEvent Mesh, Business EventsDifferent protocols
AuthenticationOAuth 2.0OAuth 2.0 + CSRF + Basic + X.509SAP requires CSRF overlay
SandboxFull + Partial copiesQuality/Dev systemsSF sandboxes easier to create
API VersioningNumbered (v62.0), 3yr supportRelease-based (2408)SF more predictable
Concurrent Requests25 (hard cap)ConfigurableSF has hard cap; SAP tunable

Important Caveats

Related Units