Yes. The Salesforce SOAP API accepts an OAuth 2.0 access token in place of the session ID that login() used to return. You get the token from an OAuth flow, put it in the sessionId field of the SessionHeader, and send the request to the customer org's own instance URL. The rest of your SOAP code, the WSDL, the calls and the parsing, stays the same.
For a SaaS company this has stopped being optional. Salesforce is retiring SOAP API login() in the Summer '27 release, so a product that still signs in to each customer org with a username, password and security token has a deadline in every one of those orgs. The work is less about the SOAP envelope than about how each customer authorizes your product and how you keep hundreds of tokens alive. Appnigma builds customer-facing integrations for B2B SaaS teams. With our integration infrastructure, each customer authorizes their own account from your product and their connection is managed separately. Our done for you delivery builds the connection to each customer's system "with the right APIs, authentication, and platform-specific code."
Updated October 2026. Checked against the SOAP API Developer Guide, Salesforce Help and the Winter '27 release notes. The earlier version said login() was still a fine option, listed the username-password flow as a choice, said the JWT bearer flow gives you refresh tokens, and recommended Named Credentials for an outside system calling in. None of those hold today. This update adds the retirement timeline, the flow choice for products that connect to many customer orgs, and the errors teams hit after the switch.
What changes in a SOAP request when you swap login() for OAuth
Only two things in the request change. The value in SessionHeader now comes from an OAuth token response instead of a LoginResult, and the endpoint is built from that response's instance_url. Salesforce's error reference says it directly. "For SOAP API, use the SessionHeader SOAP authorization header. The access token is placed in the header" (Core Data Types Used in API Calls).
| SOAP login() | OAuth 2.0 access token | |
|---|---|---|
| What your product stores per customer | Username, password, security token | Refresh token or a signing key, plus the org's instance URL |
| Where SessionHeader's value comes from | LoginResult.sessionId | access_token from the token endpoint |
| Where requests go | LoginResult.serverUrl | instance_url + /services/Soap/u/<version> |
| Works in API 65.0 and later | No | Yes |
| Hourly limit | 3,600 login() calls per user | No login() calls at all |
| Status | Retiring in Summer '27 | Supported |
The endpoint paths are the same ones Salesforce lists in the identity response, /services/Soap/u/{version} for the partner WSDL, /services/Soap/c/{version} for the enterprise WSDL and /services/Soap/m/{version} for the Metadata API (Using Identity URLs). Don't keep sending calls to login.salesforce.com. "The login server supports only login calls," per the login() reference.
Here is a partner WSDL query sent with an OAuth token. The token and instance URL come from environment variables, and API version 66.0 is used because OAuth tokens work in versions where login() no longer exists.
curl -s "$SF_INSTANCE_URL/services/Soap/u/66.0" \
-H 'Content-Type: text/xml; charset=UTF-8' \
-H 'SOAPAction: ""' \
--data-binary @- <<EOF
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:urn="urn:partner.soap.sforce.com">
<soapenv:Header>
<urn:SessionHeader>
<urn:sessionId>$SF_ACCESS_TOKEN</urn:sessionId>
</urn:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<urn:query>
<urn:queryString>SELECT Id, Name FROM Account LIMIT 5</urn:queryString>
</urn:query>
</soapenv:Body>
</soapenv:Envelope>
EOFIn Java code built on the Web Service Connector (WSC), the change is in the connection config. You stop calling login() and set the token and endpoint yourself. The Metadata API sample in Salesforce's docs uses the same pattern and tells you to "pass the returned access token instead of the session ID" (Java Sample Code for File-Based Development).
ConnectorConfig config = new ConnectorConfig();
config.setSessionId(System.getenv("SF_ACCESS_TOKEN"));
config.setServiceEndpoint(System.getenv("SF_INSTANCE_URL") + "/services/Soap/u/66.0");
PartnerConnection connection = Connector.newConnection(config);
QueryResult result = connection.query("SELECT Id, Name FROM Account LIMIT 5");The integration user still needs the API Enabled permission. Salesforce notes that "to access the API to issue calls and receive the call results, a user must have the API Enabled permission" (Security and the API). OAuth changes how the user signs in, not what that user is allowed to read.
Is the Salesforce SOAP API login() call being retired?
Yes, the login() call is retiring, and the SOAP API itself isn't. The login() reference says "Salesforce is retiring SOAP API login() in the Summer '27 release," that it "remains available in API version 64.0 and earlier," and that "it is disabled by default for orgs created in Winter '26 and later." Every other SOAP call keeps working once you authenticate with OAuth.
Salesforce's retirement article, Platform SOAP API login() Retirement, sets out steps before the final date. These are the ones a vendor's integration user runs into.
| When | What changes for login() |
|---|---|
| Winter '26 | Disabled by default in newly created orgs |
| API 65.0 and later | "SOAP API login() isn't available" |
| December 1, 2026 | login() "succeeds only if the user has Use Any API Auth user permission assigned" |
| December 15, 2026 sandboxes, January 31, 2027 production | Logins from a broad IP range need a security token, and orgs with no login() use in six months have it turned off |
| Summer '27 | Calls in API 31.0 through 64.0 "will no longer be supported and will stop working" |
Two of these steps hit SaaS products before Summer '27. The first is the permission. A customer whose admin hasn't assigned Use Any API Auth to your integration user starts getting failures in December 2026. The login() reference says "attempts to authenticate without this permission result in an INSUFFICIENT_ACCESS error." The second is new customers. Any org created in Winter '26 or later starts with login() off, so a prospect on a new org can't connect to a password-based integration at all without changing a setting their security team may refuse.
The article also names the replacement. "For server-to-server or headless integration, use either the OAuth 2.0 JWT bearer flow or the OAuth 2.0 client credentials flow." It tells customers of third-party tools to contact the vendor, which means your support queue is where these questions land. To test before the date, a customer admin can open Release Updates in Setup and select Enable Test Run on the login() retirement update, which "blocks all SOAP API login() calls to the org."
Which OAuth flow replaces login() in a SaaS product
The right flow depends on who authorizes the connection. When your product connects to many customer orgs, the customer's admin should approve your app from inside your product, and your product should keep a refresh token for that org. That is the web server flow, also called the authorization code flow, with PKCE (Proof Key for Code Exchange, a check that the code exchange comes from the client that started it).
The JWT bearer and client credentials flows suit a single org you control, such as your own production org or an internal job. Neither one hands you a refresh token. Salesforce says the JWT bearer flow "never issues a refresh token" (OAuth 2.0 JWT Bearer Flow), and the client credentials flow "doesn't support refresh tokens" (OAuth 2.0 Client Credentials Flow). Both also need setup inside the customer org. JWT bearer needs a certificate on the app and a pre-approved user. Client credentials needs an External Client App in that org with a run-as user assigned. For a vendor, that is a setup task per customer.
| Flow | Who approves | Refresh token | Fits a SaaS product connecting to customer orgs |
|---|---|---|---|
| Web server with PKCE | The customer, through a consent screen | Yes, with the refresh_token scope | Yes, the default choice |
| JWT bearer | An admin pre-authorizes the user | Never issued | Only if each customer installs your certificate and approves the user |
| Client credentials | An admin assigns a run-as user | Not supported | Only for one org you own |
| Username-password | Nobody, credentials are sent directly | Not supported | No |
The username-password OAuth flow looks like the shortest path off login() because it takes the same credentials. It isn't a fix. Salesforce Help says "if your org is created in Summer '23 or later, the username-password flow is blocked by default" (Block Authorization Flows). External Client Apps don't offer it, and the Winter '27 release notes retire it for Connected Apps too (Retirement of OAuth 2.0 Username-Password Flow for Connected Apps). Our guide on which OAuth flow fits a SaaS integration goes through the settings for each one.
Register the app as an External Client App. In Setup, enter External in Quick Find and "select External Client App Manager," then New External Client App (Set Up External Client Apps). The old App Manager route creates Connected Apps, and "creating connected apps is restricted as of Spring '26" (Authorization Through External Client Apps or Connected Apps and OAuth 2.0). For the SOAP API, request the api scope, and request refresh_token as well, because Salesforce says "you must explicitly request the refresh_token scope to get a refresh token" (Scope Parameter Values). If you already run a Connected App, the steps to move a packaged Connected App to an External Client App keep your consumer key, so customers don't reauthorize.
Running SOAP calls across many customer orgs with OAuth
The single-org examples in most guides skip the parts that break first in a multi-org product. Your token store needs one row per customer org, keyed by the org ID from the token response, holding that org's refresh token and instance URL. Customer orgs live on different instances and My Domains, so a hard-coded endpoint sends one customer's calls to another's host. Our guide to storing one connection per customer org covers the storage side.
Access tokens expire after a period of inactivity set by the customer's session timeout. "The default is 120 minutes (two hours)," and each API call resets the timer, per Error Handling. The org's admin can change it, so don't schedule refreshes on a fixed clock. Treat an INVALID_SESSION_ID fault as the signal. Refresh once, retry the call once, and mark the connection broken if the retry fails too.
Refresh tokens now expire as well. Salesforce's Winter '27 notes say "refresh tokens expire if they haven't been used for 30 days," in sandboxes from October 21, 2026 and in production from November 4, 2026 (Refresh Token Idle TTL Is Limited to 30 Days). A customer who pauses a sync for a month comes back to a dead connection. A scheduled refresh for every connected org inside that window keeps it alive. If your app is used by more than two customer production orgs, the ISVforce Guide also asks for PKCE, refresh token rotation, the 30-day idle limit and a refresh token IP allowlist (Secure Your Connected Apps and External Client Apps). Rotation means every refresh returns a new refresh token, and your store has to save it before the old one is used again. Our post on refresh token rotation and the 30-day idle limit covers those controls.
A worked example from the documented limits shows why some SOAP clients fail under load even before the retirement. login() is capped at "3,600 calls to login() per user per hour." A worker that signs in before every call and runs one call a second uses 3,600 logins in an hour. The next login fails with "Login Rate Exceeded," and Salesforce blocks that user for an hour (login() reference). Two workers sharing one integration user reach the cap in 30 minutes. OAuth removes that failure, because the worker reuses a token until it expires and never calls login() at all.
Errors teams hit after switching SOAP to OAuth
Most failures after the move come from the token or the endpoint, not the SOAP body. These are the ones to map in your error handling.
- INVALID_SESSION_ID. The token is "malformed (incorrect length or format) or has expired," per Core Data Types Used in API Calls. It also shows up when a call goes to the wrong instance.
- INVALID_AUTH_HEADER. The SessionHeader is missing or in the wrong namespace. SessionHeader has needed the API namespace from the WSDL since version 12.0 (SessionHeader).
- invalid_grant on refresh. Salesforce lists an "expired access/refresh token" among its causes (OAuth Authorization Errors). After November 4, 2026, a 30-day idle token is the usual one. The customer has to authorize again.
- redirect_uri_mismatch. The callback URL in the request doesn't match the app definition. This is the callback error, not
invalid_grant. - INSUFFICIENT_ACCESS from `login()`. A client you haven't migrated yet is running without Use Any API Auth. Find it before December 1, 2026.
One fix that comes up in older guides doesn't apply. Named Credentials are for callouts that start inside Salesforce. "Salesforce manages all authentication for Apex callouts that specify a named credential," per Named Credentials as Callout Endpoints. Your product calling into a customer org needs OAuth tokens on your side. More error codes and fixes are in our Salesforce OAuth errors reference.
Frequently Asked Questions
Can you use OAuth 2.0 with the Salesforce SOAP API?
Yes. Put the OAuth access token in the sessionId field of the SOAP SessionHeader and send the call to the org's instance_url plus /services/Soap/u/<version>.
When does Salesforce retire SOAP API login()?
In the Summer '27 release, for API versions 31.0 through 64.0. It isn't available in 65.0 and later, and it is off by default in orgs created in Winter '26 or later.
Which OAuth flow should replace login() for server-to-server SOAP calls?
For one org you own, the JWT bearer or client credentials flow. For a SaaS product connecting to customer orgs, the web server flow with PKCE, because it is the one that returns a refresh token.
Does the username-password OAuth flow solve the login() retirement?
No. It is blocked by default in orgs created in Summer '23 or later, External Client Apps don't offer it, and Salesforce is retiring it for Connected Apps.
Can Appnigma move our SOAP integration off login()?
Yes. Appnigma's done for you delivery builds and maintains customer-facing integrations with the right APIs and authentication, and integration infrastructure manages each customer's authorization and connection separately.
Related Articles
- External Client App in Salesforce
- Salesforce External Client App OAuth 2.0 Settings
- Connected App vs External Client App
- Salesforce Secure Connector for SaaS Products
- Salesforce OAuth Errors and Troubleshooting
- Salesforce Connected App Security Requirements
Sources
- Salesforce Developers, SOAP API Developer Guide, login()
- Salesforce Help, Platform SOAP API login() Retirement
- Salesforce Developers, SOAP API Developer Guide, Core Data Types Used in API Calls
- Salesforce Developers, SOAP API Developer Guide, SessionHeader
- Salesforce Developers, SOAP API Developer Guide, Authorization Through External Client Apps or Connected Apps and OAuth 2.0
- Salesforce Developers, SOAP API Developer Guide, Security and the API
- Salesforce Developers, SOAP API Developer Guide, Error Handling
- Salesforce Developers, Mobile SDK Development Guide, Using Identity URLs
- Salesforce Developers, Mobile SDK Development Guide, Scope Parameter Values
- Salesforce Developers, Metadata API Developer Guide, Java Sample Code for File-Based Development
- Salesforce Developers, Set Up External Client Apps
- Salesforce Developers, ISVforce Guide, Secure Your Connected Apps and External Client Apps
- Salesforce Developers, Apex Developer Guide, Named Credentials as Callout Endpoints
- Salesforce Help, OAuth 2.0 JWT Bearer Flow for Server-to-Server Integration
- Salesforce Help, OAuth 2.0 Client Credentials Flow for Server-to-Server Integration
- Salesforce Help, Block Authorization Flows to Increase Security
- Salesforce Help, OAuth Authorization Errors
- Winter '27 Release Notes, Refresh Token Idle TTL Is Limited to 30 Days
- Winter '27 Release Notes, Retirement of OAuth 2.0 Username-Password Flow for Connected Apps



