Callouts are how Salesforce talks to the rest of the world. REST callouts in Apex are straightforward once you understand the three things that trip people up: Remote Site Settings, Named Credentials, and test mocking. This guide covers all of them.
Prerequisites
Before any callout works, the target endpoint must be whitelisted. Go to Setup → Remote Site Settings and add the base URL. Without this, Salesforce throws a System.CalloutException regardless of your code.
Better: use Named Credentials instead of Remote Site Settings. More on that below.
The Basic GET Callout
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/data');
req.setMethod('GET');
req.setHeader('Authorization', 'Bearer ' + token);
req.setTimeout(10000); // 10 seconds max
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() == 200) {
Map<String, Object> body =
(Map<String, Object>) JSON.deserializeUntyped(res.getBody());
// process body
} else {
throw new CalloutException('API error: ' + res.getStatus());
}
POST with a JSON Body
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/orders');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String, Object>{
'orderId' => '12345',
'amount' => 99.99
}));
HttpResponse res = new Http().send(req);
Use Named Credentials
Hard-coding tokens in Apex is a security risk and a maintenance nightmare. Named Credentials store credentials securely and let you reference them by name:
req.setEndpoint('callout:My_Named_Credential/data');
Salesforce substitutes the base URL and auth headers automatically. The credential is managed in Setup — developers never touch the actual secret. See the Named Credentials guide for full setup steps.
Callouts in Triggers
You cannot make a synchronous callout from a trigger. Use @future(callout=true) or a Queueable with Database.AllowsCallouts:
public class OrderSyncQueueable implements Queueable, Database.AllowsCallouts {
private List<Id> orderIds;
public OrderSyncQueueable(List<Id> ids) { this.orderIds = ids; }
public void execute(QueueableContext ctx) {
// make callout here
}
}
Mocking Callouts in Tests
Callouts do not execute in test context — you must mock them. Implement HttpCalloutMock:
@isTest
global class MockHttpResponse implements HttpCalloutMock {
global HTTPResponse respond(HTTPRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(200);
res.setBody('{"status":"ok"}');
return res;
}
}
@isTest
static void testCallout() {
Test.setMock(HttpCalloutMock.class, new MockHttpResponse());
// call your class
}
Error Handling Pattern
Never let a callout fail silently. At minimum: log the status code and body to a custom object or platform event, and rethrow or handle gracefully based on whether the failure is transient (5xx) or permanent (4xx).