Callouts & Integration
27.1 HTTP callouts
Callout rules:
Endpoints must be authorized via a Named Credential (recommended) or a Remote Site Setting.
No callouts after uncommitted DML in the same transaction (do callouts first, or go async).
Synchronous triggers can't call out — use Queueable/@future(callout=true).
100 callouts per transaction; 120 s cumulative timeout; request/response size up to 6 MB (sync) / 12 MB (async).
Long-running callouts from Visualforce/Aura can use the Continuation class.
public with sharing class WeatherService {
public class WeatherException extends Exception {}
public static Decimal currentTemp(String city) {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:Weather_API/v1/current?city=' + EncodingUtil.urlEncode(city, 'UTF-8'));
req.setMethod('GET');
req.setHeader('Accept', 'application/json');
req.setTimeout(20000); // ms; max 120,000
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) {
throw new WeatherException('Weather API returned ' + res.getStatusCode() + ': ' + res.getStatus());
}
Map<String, Object> body = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
return (Decimal) body.get('tempC');
}
}27.2 Named Credentials and External Credentials
The modern model (Winter '23+) separates where from how to authenticate:
Named Credential Endpoint URL, callout options (generate auth header, allow formulas in headers/body), link to an External Credential External Credential
Authentication protocol (OAuth 2.0 client credentials / browser flow / JWT, AWS Signature v4, Custom, Basic via custom headers) and principals (named or per-user)
The actual identity/secret; access granted via permission sets Custom headers e.g., x-api-key: {!$Credential.Weather_Ext_Cred.ApiKey}
Using callout:Weather_API keeps secrets out of code, supports per-environment endpoints, and lets admins rotate credentials without deployments. Users (or the integration user running async jobs) need permission-set access to the External Credential principal.
27.3 JSON serialization
Tip: paste a JSON sample into a JSON-to-Apex generator (e.g., the community JSON2Apex tool) to create DTO classes quickly.
// typed classes (DTOs) — preferred
public class OrderDto {
public String orderId;
public Decimal total;
public List<LineDto> lines;
public Datetime placedAt;
}
public class LineDto {
public String sku;
public Integer qty;
}
String payload = '{"orderId":"A-1","total":99.5,"lines":[{"sku":"X","qty":2}],"placedAt":"2025-03-01T10:00:00Z"}';
OrderDto order = (OrderDto) JSON.deserialize(payload, OrderDto.class);
String out = JSON.serialize(order); // compact
String pretty = JSON.serializePretty(order);
String noNulls = JSON.serialize(order, true); // suppress null fields
// untyped — for dynamic or partial structures
Map<String, Object> m = (Map<String, Object>) JSON.deserializeUntyped(payload);
List<Object> lines = (List<Object>) m.get('lines');
Map<String, Object> firstLine = (Map<String, Object>) lines[0];
// strict parsing: fail on unknown fields
OrderDto strict = (OrderDto) JSON.deserializeStrict(payload, OrderDto.class);
// reserved words as JSON keys (e.g., "number", "type"): rename in the string or use JSONParser
String fixedJson = payload.replace('"number":', '"number_x":');
// streaming generation
JSONGenerator gen = JSON.c
…27.4 A reusable REST client with POST
public inherited sharing class ErpClient {
public class ErpException extends Exception {}
public static String createOrder(OrderDto order) {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ERP/api/orders');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(order, true));
req.setTimeout(30000);
HttpResponse res = new Http().send(req);
Integer code = res.getStatusCode();
if (code == 201 || code == 200) {
Map<String, Object> body = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
return (String) body.get('id');
} else if (code == 429 || code >= 500) {
throw new ErpException('Retryable error ' + code); // caller can retry asynchronously
}
throw new ErpException('ERP rejected order: ' + code + ' ' + res.getBody());
}
}27.5 SOAP callouts
Generate Apex stubs from a WSDL: *Setup → Apex Classes → Generate from WSDL* (WSDL2Apex). The generated class has a port type with methods you call directly; set endpoint_x to 'callout:My_Named_Cred/path'. Test with WebServiceMock. For complex or unsupported WSDLs, build the XML manually with Dom.Document and send it via HttpRequest.
27.6 Integration patterns
Request–reply (synchronous) Apex callout from LWC controller/Flow action
Platform Event, Queueable callout, outbound message Batch data sync
Batch Apex callouts, Bulk API 2.0 from the external system Standard REST/SOAP APIs, Apex REST (§28), Composite API UI update from data change CDC, Platform Events + empApi
Salesforce Connect (external objects, __x) Large integrations / API management MuleSoft Anypoint Platform