SfSkills Domains Docs GitHub

Home / integration / soap-api-patterns

soap-api-patterns

Use when designing, implementing, or reviewing Salesforce SOAP API integrations — covering enterprise vs partner WSDL. Trigger keywords: 'enterprise WSDL', 'partner WSDL', 'login() call', 'sessionId', 'SOAP API', 'WSC', 'force-wsc', 'Ant Migration Tool', '.NET SOAP integration'. NOT for REST API, Bulk API 2 — use apex/apex-wsdl2apex-patterns.

Open the skill folder on GitHub


SOAP API Patterns

Use this skill when the integration task involves the Salesforce SOAP API — whether you are selecting between the enterprise and partner WSDLs, implementing a login-and-session lifecycle, calling core DML or query operations, or working with the Metadata API (which is exclusively SOAP-based). This skill also covers the decision of when SOAP is the right choice over REST.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

Enterprise WSDL vs Partner WSDL

The Salesforce SOAP API ships two distinct WSDLs with different type contracts:

Enterprise WSDLPartner WSDL
Type contractStrongly typed — each object and field has a generated Java/C# classWeakly typed — generic sObject with string-keyed field maps
Org-specificityOrg-specific — generated from your org's schema, includes custom objects and fieldsGeneric — works with any Salesforce org without regeneration
When to useInternal integrations for a specific org where developer productivity and compile-time safety matterISVs, packages, and cross-org integrations that must work against multiple or unknown orgs
WSDL endpointSetup > API > Generate Enterprise WSDLSetup > API > Generate Partner WSDL
SOAP service URL/services/Soap/c/<version>//services/Soap/u/<version>/
Critical drawbackMust be re-downloaded and regenerated every time custom fields or objects are added or changedDeveloper must resolve field metadata at runtime; relationship queries need a describeSObjects() discovery step first (see Relationship Queries with the Partner WSDL)

Both WSDLs expose the same operations (login, query, create, update, upsert, delete, etc.). The difference is entirely in the type surface exposed to the client.

The Metadata API also requires WSDL-based access. Authentication is established via the enterprise or partner WSDL login() call first, and the resulting sessionId is then used to initialize the Metadata API connection.

Authentication: login() and Session Management

The canonical SOAP authentication flow calls login() on the SOAP endpoint and extracts two values from the LoginResult:

  1. sessionId — the session token included in every subsequent SOAP call via the CallOptions or SessionHeader SOAP header.
  2. serverUrl — the instance-specific URL to use for all subsequent API calls. The login endpoint is a generic URL; after login you must switch to the serverUrl. Failing to do so causes calls to fail on sandbox and non-na1 instances.
<!-- SOAP login request (partner WSDL) -->
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:urn="urn:partner.soap.sforce.com">
  <soapenv:Body>
    <urn:login>
      <urn:username>user@example.com</urn:username>
      <urn:password>passwordSECURITYTOKEN</urn:password>
    </urn:login>
  </soapenv:Body>
</soapenv:Envelope>

The response LoginResult contains:

Session expiry: Sessions expire after the org's configured timeout (default 2 hours for most editions, configurable from 15 minutes to 24 hours in Setup > Session Settings). Expired sessions return INVALID_SESSION_ID. Integrations must catch this fault and re-authenticate — either by calling login() again or by refreshing the OAuth access token if using OAuth session injection.

Security token: When calling login() with username/password from an IP not in the org's trusted range, the security token must be appended directly to the password string (no separator). Use OAuth flows to avoid embedding credentials.

Core SOAP Operations

OperationDescriptionNotes
query()Execute a SOQL query; returns up to queryBatchSize recordsReturns QueryResult with done flag and queryLocator
queryMore()Fetch the next page of a query() resultRequired when done is false; use the queryLocator from prior result
create()Insert one or more sObjects (up to 200 per call)Returns SaveResult[] with per-record success/error
update()Update one or more sObjects by Salesforce ID (up to 200 per call)Returns SaveResult[]
upsert()Create-or-update using an external ID field (up to 200 per call)Requires an indexed External ID field; returns UpsertResult[]
delete()Delete one or more records by ID (up to 200 per call)Soft delete; records go to Recycle Bin
merge()Merge up to 3 records into a master recordReturns MergeResult; merged records are deleted
retrieve()Fetch specific fields for a list of record IDsEfficient alternative to query when IDs are known
getUpdated() / getDeleted()Retrieve IDs of records changed or deleted in a time windowUseful for CDC-style polling when CDC/Streaming is not available

Each DML call accepts an array of up to 200 records. Salesforce processes all records in the call even if some fail — there is no all-or-none semantics by default (unlike REST Composite with allOrNone: true). Inspect each SaveResult or UpsertResult for per-record success and errors.

Relationship Queries with the Partner WSDL

Relationship SOQL — a parent-to-child subquery like SELECT Id, (SELECT Id FROM Assets) FROM Account, or a child-to-parent dot-walk like SELECT Id, Owner.Name FROM Case — behaves differently under the partner WSDL because of its loosely-typed design. The partner WSDL "defines a single, generic object (sObject) that represents all the objects," so it "doesn't contain the detailed type information that's available in the enterprise WSDL which you need for a relationship SOQL query." The relationship names and reference-field names an enterprise-WSDL client reads from generated classes simply are not in the partner stubs.

The partner-WSDL workflow is therefore describe-first:

  1. Call describeSObjects() for the object you intend to query. The returned DescribeSObjectResult carries the relationship metadata the WSDL omits.
  2. Parent-to-child (one-to-many) subqueries — read the relationshipName from the child relationships and use it as the subquery source. Example: Assets is the relationshipName for the child assets of an Account, giving SELECT Id, (SELECT Id, Name FROM Assets) FROM Account.
  3. Child-to-parent traversal — identify the reference (lookup/polymorphic) fields, such as WhoId, WhatId, or OwnerId on a Lead, Case, or custom object, and dot-walk through them: SELECT Id, Owner.Name FROM Case.

Two follow-on traps come from the generic representation:

Enterprise-WSDL clients avoid all of this: relationship names and typed accessors are baked into the generated classes, so relationship queries compile against known types with no describe step.

When to Use SOAP Over REST

Prefer REST API for new integrations — it is simpler, more widely supported, and the Salesforce product direction favors it. Use SOAP API when:

  1. Metadata API is involved — The Metadata API is exclusively SOAP. Deployments via the Ant Migration Tool, Java/WSC-based deploy tools, and any retrieve/deploy operation must use SOAP.
  2. Legacy .NET or Java codebase already using WSDL-generated stubs — Migrating a working integration to REST has cost and risk. Maintaining SOAP is reasonable if the integration is stable.
  3. Enterprise-grade SOAP toolkit ecosystem — WS-Security, SOAP intermediaries, and certain ESB/middleware products (MuleSoft, IBM MQ, SAP PI) have first-class SOAP connectors. Using REST in these environments may require lower-level HTTP configuration.
  4. Compound field access on Address/Geolocation — SOAP returns Address and Geolocation as nested complex types in the response, which some ETL tools handle better than the REST API's flat-field representation.

Common Patterns

Mode 1 — Implement a New SOAP Integration

Step 1 — Choose WSDL type. Internal to a single org? Use enterprise WSDL for compile-time safety. ISV or multi-org product? Use partner WSDL.

Step 2 — Download and generate stubs. In Setup > API, download the WSDL file. Use the platform's SOAP toolkit to generate client classes. For Java with WSC:

java -classpath force-wsc-XX.X.X-uber.jar \
  com.sforce.ws.tools.wsdlc enterprise.wsdl enterprise.jar

Step 3 — Implement login() and capture serverUrl + sessionId.

ConnectorConfig config = new ConnectorConfig();
config.setAuthEndpoint("https://login.salesforce.com/services/Soap/c/63.0");
config.setUsername("user@example.com");
config.setPassword("passwordSECURITYTOKEN");

EnterpriseConnection connection = Connector.newConnection(config);
// config.getServiceEndpoint() now contains the instance serverUrl
// connection carries the sessionId automatically via WSC

Step 4 — Execute operations and inspect per-record results.

SObject account = new SObject();
account.setType("Account");
account.setField("Name", "Acme Corp");

SaveResult[] results = connection.create(new SObject[]{ account });
for (SaveResult result : results) {
    if (!result.isSuccess()) {
        for (Error error : result.getErrors()) {
            System.err.println(error.getStatusCode() + ": " + error.getMessage());
        }
    }
}

Step 5 — Implement session expiry handling. Catch InvalidSObjectFault / INVALID_SESSION_ID faults, call login() again, and retry.

Mode 2 — Review an Existing SOAP Integration

Check for these issues in priority order:

  1. Is serverUrl used after login? Hardcoded production endpoints break sandbox or EU instances. The serverUrl from LoginResult must be the base for all subsequent calls.
  2. Is the API version current? Endpoint URLs containing /Soap/c/20.0 through /Soap/c/40.0 are in the deprecated range. Salesforce retires API versions; update to v56.0 or later.
  3. Are SaveResult[] / UpsertResult[] errors inspected per record? SOAP DML calls partially succeed — checking only the HTTP response code or outer fault misses record-level failures.
  4. Is the enterprise WSDL up to date? If custom fields or objects were added since the last WSDL regeneration, those fields are inaccessible from the stubs.
  5. Are credentials hardcoded? login() password with embedded security token must not be stored in source code.
  6. Is queryMore() called when done is false? Failing to paginate silently truncates query results.
  7. For partner-WSDL relationship queries: are relationshipName and reference-field names discovered via describeSObjects()? Partner-WSDL clients have no compile-time relationship metadata, and nested results must be type-resolved from describe metadata rather than cast to a generated class.

Mode 3 — Troubleshoot a SOAP Integration

SymptomLikely CauseFix
INVALID_SESSION_ID faultSession expired or wrong session headerRe-authenticate with login() and update SessionHeader
UNSUPPORTED_API_VERSION (500)API version in endpoint URL is retiredUpdate endpoint URL to a current version (v56.0+)
Records found in login org, not in data orgserverUrl ignored; still using login endpointSwitch all post-login calls to serverUrl from LoginResult
Custom fields return null or don't exist in stubsEnterprise WSDL was not regenerated after field creationRe-download enterprise WSDL and regenerate stubs
REQUIRED_FIELD_MISSING on createOrg validation rule or required field not set in requestInspect SaveResult.errors for specific field names
queryMore() returns faultqueryLocator from prior page is stale (session reset)Handle session expiry and re-execute query from scratch
Session expires mid-batchDefault session timeout reached during long operationsIncrease session timeout in Setup > Session Settings or implement proactive re-authentication

Decision Guidance

SituationRecommended ApproachReason
New integration from scratchREST APISimpler, JSON-native, broader modern tooling support
Metadata deployments (Ant Migration Tool, CI/CD deploy)SOAP (Metadata API)Metadata API is exclusively SOAP-based
Existing .NET/Java codebase with WSDL stubsMaintain SOAP APIRewrite cost outweighs benefit unless integration is failing
ISV building a managed package for any orgPartner WSDLWorks without regeneration across all customer orgs
Internal integration for a known single orgEnterprise WSDLStrongly typed, compile-time safety, auto-generated classes
Auth from untrusted IP with username/passwordAppend security token to passwordSalesforce requires token when IP not in trusted range
Auth in production systemsOAuth JWT Bearer or Web Server flowAvoids embedding credentials; tokens are scoped and revocable
Query result > configured queryBatchSizequery() + queryMore() loopquery() returns partial results; must call queryMore() until done = true

Step-by-step instructions for an AI agent or practitioner activating this skill:

  1. Gather context — confirm the org edition, relevant objects, and current configuration state
  2. Review official sources — check the references in this skill's well-architected.md before making changes
  3. Implement or advise — apply the patterns from Core Concepts and Common Patterns sections above
  4. Validate — run the skill's checker script and verify against the Review Checklist below
  5. Document — record any deviations from standard patterns and update the template if needed

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. Enterprise WSDL becomes stale when org schema changes — The enterprise WSDL is generated from your org's exact schema at a point in time. Adding or changing a custom field after generation makes that field invisible to the generated stubs. The integration will not error — it will silently omit the field. Regenerate the WSDL and rebuild stubs any time the org schema changes.
  2. serverUrl must replace the login endpoint for all subsequent calls — The login() call targets login.salesforce.com (or test.salesforce.com for sandbox). The LoginResult.serverUrl is the instance-specific URL you must use for all subsequent SOAP calls. Ignoring serverUrl and continuing to use the login host causes requests to fail on any instance other than na1, including all EU, APAC, and Hyperforce instances.
  3. Session expiry kills in-progress batch operations — If a query() + queryMore() loop runs longer than the org's session timeout, the queryLocator becomes invalid and queryMore() returns INVALID_SESSION_ID. The entire query must restart from scratch after re-authentication. Mitigation: set a longer session timeout in Setup > Session Settings, or break large queries into smaller date-range slices that complete within a single session window.
  4. SOAP DML calls partially succeed — there is no implicit rollback — Unlike REST Composite with allOrNone: true, a SOAP create() or update() call with 200 records will process all records regardless of individual failures. The response array contains a SaveResult per record; some will have success=true and others success=false. Integrations that only check the HTTP 200 outer response will miss failed records.
  5. Security token appended, not separate — The login() password parameter is the user's password with the security token appended as a single string (e.g., mypasswordXYZTOKEN). There is no separator. A common mistake is passing the token as a separate parameter, which results in a LOGIN_MUST_USE_SECURITY_TOKEN fault.

Output Artifacts

ArtifactDescription
WSDL selection rationaleEnterprise vs partner decision with justification for the integration context
Login flow implementationlogin() call pattern, serverUrl capture, and SessionHeader wiring
Operation call structureCreate/update/upsert/delete/query call scaffolds with per-record result inspection
Session expiry handlerFault detection and re-authentication pattern
Review findingsItemized issues from the Mode 2 review checklist with severity and remediation

Package files