WebServiceCallout.invoke to send requests and parse responses with minimal manual parsing.
While lightweight REST integrations with JSON payloads dominate modern web APIs, many mission-critical enterprise systems—including legacy ERPs, banking networks, and government data exchanges—rely strictly on the SOAP (Simple Object Access Protocol) standard. Salesforce Apex provides native support for both consuming external SOAP services via WSDL2Apex callouts and exposing custom Apex methods as SOAP web services.
1. Understanding the Two Roles of SOAP in Apex
When working with SOAP in Salesforce development, you encounter two distinct patterns:
- Exposing Custom Web Services: Using the
webservicekeyword on static methods inside aglobalApex class allows external clients to download your custom WSDL from Salesforce Setup and trigger Apex logic remotely. - Consuming External SOAP Services (Callouts): Downloading an external system's WSDL file and generating client-side proxy classes via Setup > Apex Classes > Generate from WSDL to initiate outbound XML web service requests.
- Standard Protocol: XML-based messaging governed by a formal WSDL contract.
- Apex Callout Method:
WebServiceCallout.invokein generated stub classes. - Authentication Standard: WS-Security, HTTP Basic, or OAuth managed securely via Named Credentials.
- Synchronous Timeout Limit: Maximum 120-second timeout per callout.
2. Consuming an External SOAP Service (Outbound Callout)
When you import an external WSDL document, Salesforce creates an Apex representation of the service. Below is a structured example of how the generated proxy class and consumer service execute an outbound SOAP callout to create a contact record in an external master system.
ExternalContactSoapService.cls)
// Auto-generated or custom-structured WSDL stub
public with sharing class ExternalContactSoapService {
public class ContactRequest_element {
public String firstName;
public String lastName;
public String email;
private String[] firstName_type_info = new String[]{'firstName', 'http://services.external.com/contact', null, '1', '1', 'false'};
private String[] lastName_type_info = new String[]{'lastName', 'http://services.external.com/contact', null, '1', '1', 'false'};
private String[] email_type_info = new String[]{'email', 'http://services.external.com/contact', null, '0', '1', 'false'};
private String[] apex_schema_type_info = new String[]{'http://services.external.com/contact', 'true', 'false'};
private String[] field_order_type_info = new String[]{'firstName', 'lastName', 'email'};
}
public class ContactResponse_element {
public Boolean success;
public String externalId;
public String errorMessage;
private String[] success_type_info = new String[]{'success', 'http://services.external.com/contact', null, '1', '1', 'false'};
private String[] externalId_type_info = new String[]{'externalId', 'http://services.external.com/contact', null, '0', '1', 'false'};
private String[] errorMessage_type_info = new String[]{'errorMessage', 'http://services.external.com/contact', null, '0', '1', 'false'};
private String[] apex_schema_type_info = new String[]{'http://services.external.com/contact', 'true', 'false'};
private String[] field_order_type_info = new String[]{'success', 'externalId', 'errorMessage'};
}
public class ContactPort {
// Use Named Credentials endpoint for secure authentication
public String endpoint_x = 'callout:External_Contact_Service/services/soap/v1';
public Map<String, String> inputHttpHeaders_x;
public Integer timeout_x = 60000; // 60-second timeout
public ContactResponse_element createExternalContact(String firstName, String lastName, String email) {
ContactRequest_element request_x = new ContactRequest_element();
request_x.firstName = firstName;
request_x.lastName = lastName;
request_x.email = email;
ContactResponse_element response_x;
Map<String, ContactResponse_element> response_map_x = new Map<String, ContactResponse_element>();
response_map_x.put('response_x', response_x);
// Execute native SOAP callout
WebServiceCallout.invoke(
this,
request_x,
response_map_x,
new String[]{
endpoint_x,
'createContactOperation',
'http://services.external.com/contact',
'createContactRequest',
'http://services.external.com/contact',
'createContactResponse',
'ExternalContactSoapService.ContactResponse_element'
}
);
response_x = response_map_x.get('response_x');
return response_x;
}
}
}
ContactSyncService.cls)
public with sharing class ContactSyncService {
/**
* @description Synchronizes local contact data with the external SOAP endpoint
*/
public static void syncContact(Id contactId) {
if (contactId == null) {
return;
}
Contact con = [
SELECT Id, FirstName, LastName, Email
FROM Contact
WHERE Id = :contactId
WITH USER_MODE
LIMIT 1
];
try {
ExternalContactSoapService.ContactPort client = new ExternalContactSoapService.ContactPort();
ExternalContactSoapService.ContactResponse_element response = client.createExternalContact(
con.FirstName,
con.LastName,
con.Email
);
if (response.success) {
System.debug(LoggingLevel.INFO, 'External record synced. Target ID: ' + response.externalId);
} else {
System.debug(LoggingLevel.ERROR, 'External SOAP rejection: ' + response.errorMessage);
}
} catch (System.CalloutException ex) {
System.debug(LoggingLevel.ERROR, 'SOAP Callout Network or Schema Error: ' + ex.getMessage());
}
}
}
3. Exposing Custom SOAP Web Services from Apex
If you need external systems to call into Salesforce via SOAP, you can expose methods by marking them with the webservice keyword inside a global class. Once saved, generate the WSDL from Setup > Apex Classes to provide to external developers.
AccountSoapApi.cls)
global with sharing class AccountSoapApi {
global class AccountCreationResult {
webservice Boolean isSuccess;
webservice String accountId;
webservice String message;
}
webservice static AccountCreationResult createAccount(String accountName, String industry) {
AccountCreationResult result = new AccountCreationResult();
if (String.isBlank(accountName)) {
result.isSuccess = false;
result.message = 'Account Name is mandatory.';
return result;
}
try {
Account acc = new Account(
Name = accountName.trim(),
Industry = industry
);
insert as user acc;
result.isSuccess = true;
result.accountId = acc.Id;
result.message = 'Account created successfully.';
} catch (DmlException ex) {
result.isSuccess = false;
result.message = 'DML Error: ' + ex.getMessage();
}
return result;
}
}
4. Common Traps & SOAP Best Practices
Writing Apex code that constructs a SOAP client to invoke standard Salesforce APIs (such as
login.salesforce.com/services/Soap/c/...) to perform CRUD on the local org is an anti-pattern. Native Apex already has direct, secure, and governor-limit-friendly database access via standard DML statements (insert as user, update as user) and SOQL. Use SOAP callouts strictly for communicating with external third-party systems.
- Use Named Credentials: Replace hardcoded endpoint URLs with
callout:My_Named_Credentialto prevent certificate errors, avoid hardcoded secrets, and automate credential rotation. - Mock SOAP Responses in Unit Tests: Salesforce strictly prohibits live network callouts during test execution. Always implement the
WebServiceMockinterface and callTest.setMock(WebServiceMock.class, new MySoapMock())to achieve required code coverage. - Handle WSDL Schema Limitations: The native WSDL2Apex generator does not support complex XML schema constructs (such as
<xsd:choice>, multiple nested inheritance, or certain dynamic array structures). For unsupported schemas, construct raw HTTP callouts usingHttpRequestand parse the XML envelope usingDom.Document.
Summary
Integrating with SOAP web services remains a critical requirement for connecting Salesforce with enterprise legacy systems. By utilizing the built-in WSDL2Apex generator, securing endpoints via Named Credentials, and implementing defensive error handling with WebServiceMock test frameworks, developers can build scalable, rock-solid XML integrations on the Salesforce platform.