- HTTP Method (GET, POST, PUT, DELETE)
- Endpoint URL
- Headers (like
Authorization
,Content-Type
) - Body (for POST/PUT requests)
Salesforce developers live and breathe integrations, and REST API callouts are the bread and butter of connecting Salesforce with external systems. But let's be honest - REST callouts can be tricky. Whether you're syncing data with a third-party service or consuming a microservice built by your own team, mastering REST API callouts is a game-changer. So, if you've been scratching your head over timeouts, parsing JSON, or managing limits - you're not alone.
Let's dive into the 10 must-know REST API callout secrets that'll take your Salesforce dev skills to the next level.
1. Understand the Callout Anatomy - Don't Just Wing It
Before you even touch the HttpRequest
class, you need to understand what makes a REST callout tick. Every callout has these parts:
Here's a basic GET example:
// Before executing this script, ensure that the endpoint is added in Setup -> Security -> Remote Site Settings
// Endpoint: https://forceshark.com
// This script sends a GET request to the specified endpoint and logs the response body
HttpRequest req = new HttpRequest();
req.setEndpoint('https://forceshark.com/api/v1/hello-world');
req.setMethod('GET'); // Use GET method
req.setTimeout(60000); // Set timeout to 60 seconds
req.setHeader('Accept', 'application/json'); // Set Accept header to application/json
Http http = new Http();
HttpResponse res = http.send(req); // Send the request
System.debug(res.getBody()); // Log the response body
// Expected output in the debug log:
// {"method":"GET","message":"Welcome, curious explorer! ForceShark welcomes you to the observation bay 🌌🦈"}
<?xml version="1.0" encoding="UTF-8"?>
<RemoteSiteSetting xmlns="http://soap.sforce.com/2006/04/metadata">
<disableProtocolSecurity>false</disableProtocolSecurity>
<isActive>true</isActive>
<url>https://forceshark.com</url>
</RemoteSiteSetting>
If you're shooting in the dark with your request structure, you'll hit walls fast. Read the external API docs like your project depends on it (because it does).
2. Named Credentials - The Best-Kept Secret
If you're still hardcoding endpoints or storing access tokens in custom settings, you're living dangerously. Named Credentials simplify and secure your external service configurations.
You can set up the base URL, auth method (basic, OAuth 2.0, etc.), and even control access without writing a single line of Apex.
/*
Make sure to define a Principal in the External Credential and grant access via Permission Set
Setup steps required:
1. In Setup, create a Named Credential (e.g. "ForceShark") with an associated External Credential.
2. In the External Credential, define a Principal with authentication details (e.g. username/password = test).
3. Create or update a Permission Set and add access to the Principal under "External Credential Principal Access".
4. Assign the Permission Set to the user running this code.
*/
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ForceShark/api/v1/hello-world/account?id=123');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
System.debug(res.getBody());
// expected output in the debug log:
// DEBUG|{"id": "123", "name": "ForceShark", "contacts": [{"name": "Alice", "email": "alice@forceshark.com"}, {"name": "Bob", "email": "bob@forceshark.com"}]}
<?xml version="1.0" encoding="UTF-8"?>
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
<allowMergeFieldsInBody>false</allowMergeFieldsInBody>
<allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
<calloutStatus>Enabled</calloutStatus>
<generateAuthorizationHeader>false</generateAuthorizationHeader>
<label>ForceShark</label>
<namedCredentialParameters>
<parameterName>Url</parameterName>
<parameterType>Url</parameterType>
<parameterValue>https://forceshark.com</parameterValue>
</namedCredentialParameters>
<namedCredentialParameters>
<externalCredential>ForceShark</externalCredential>
<parameterName>ExternalCredential</parameterName>
<parameterType>Authentication</parameterType>
</namedCredentialParameters>
<namedCredentialType>SecuredEndpoint</namedCredentialType>
</NamedCredential>
Yep - that callout:
prefix is magic. It tells Salesforce to use the configured Named Credential for that callout. No more worrying about storing secrets or rotating tokens manually.
3. Handling JSON with JSON.deserialize()
Like a Pro
Stop manually parsing JSON with String.split()
- you're better than that. Use JSON.deserialize()
to turn your JSON responses into usable Apex objects.
Let's say your API response looks like this:
{
"id": "123",
"name": "ForceShark",
"contacts": [
{
"name": "Alice",
"email": "alice@forceshark.com"
},
{
"name": "Bob",
"email": "bob@forceshark.com"
}
]
}
You're basically turning spaghetti into structured lasagna. Clean and readable.
// Before executing this script, ensure that the endpoint is added in Setup -> Security -> Remote Site Settings
// Endpoint: https://forceshark.com
// This script sends a GET request to the specified endpoint and logs the response body
HttpRequest req = new HttpRequest();
req.setEndpoint('https://forceshark.com/api/v1/hello-world/account?id=123');
req.setMethod('GET'); // Use GET method
req.setTimeout(60000); // Set timeout to 60 seconds
req.setHeader('Accept', 'application/json'); // Set Accept header to application/json
Http http = new Http();
HttpResponse res = http.send(req); // Send the request
if (res.getStatusCode() != 200) {
System.debug('Error: ' + res.getStatus());
return;
}
AccountWrapper acc = (AccountWrapper) JSON.deserialize(res.getBody(), AccountWrapper.class);
System.debug(acc.name); // ForceShark
System.debug(acc.contacts[0].email); // alice@forceshark.com
public class AccountWrapper {
public String id;
public String name;
public List<ContactWrapper> contacts;
}
public class ContactWrapper {
public String name;
public String email;
}
4. Always Check HTTP Status Codes - No Exceptions
Don't assume your callout succeeded just because it didn't throw an exception. Always check res.getStatusCode()
.
if (res.getStatusCode() == 200) {
// all good
} else {
// something's up
System.debug('Callout failed with status: ' + res.getStatus());
}
Some APIs return 202 (Accepted), 204 (No Content), or 401 (Unauthorized), and if you're not looking, you'll miss the signs of trouble. Know what each status code means.
5. Governor Limits Are Watching - Plan Your Callouts
Salesforce doesn't mess around with limits. You get:
- 100 callouts per transaction
- 10 simultaneous long-running callouts
- Maximum timeout: 120 seconds
Be surgical with your callouts. Batch them when you can, and avoid calling out inside loops. Here's what not to do:
for (Account acc : accounts) {
doCallout(acc); // Bad idea!
}
Instead, bulkify or batch them using Queueable
or Future
methods.
6. Use Continuation
for Long-Running Callouts in Lightning
Ever tried making a callout from Lightning Components or LWC and hit a timeout wall? That's where the Continuation
class comes in. It lets you handle async callouts without hitting platform limits.
public Object makeCallout() {
Continuation con = new Continuation(40);
HttpRequest req = new HttpRequest();
req.setEndpoint('https://slowapi.example.com/data');
req.setMethod('GET');
con.addHttpRequest(req);
return con;
}
Perfect for UIs that rely on slow or bulky external data.
7. Debug Like a Detective - Log Everything
You're flying blind if you're not logging your requests and responses. Create a custom logging utility or use System.debug()
to trace:
- The endpoint URL
- Request body
- Response status and body
System.debug('Request: ' + req.getBody());
System.debug('Response: ' + res.getBody());
This helps you catch sneaky errors - like missing headers, malformed JSON, or unexpected 400s.
8. Retry Logic - Because APIs Flake Out
Sometimes APIs go down, timeout, or throw 5xx errors. Add retry logic with exponential backoff for resilience.
Basic example:
Integer attempts = 0;
Integer maxAttempts = 3;
Boolean success = false;
while (attempts < maxAttempts && !success) {
attempts++;
try {
HttpResponse res = http.send(req);
if (res.getStatusCode() == 200) {
success = true;
} else {
System.debug('Retry attempt ' + attempts);
}
} catch (Exception ex) {
System.debug('Exception on attempt ' + attempts + ': ' + ex.getMessage());
}
}
It's not pretty, but it keeps your integration alive when the network isn't your friend.
9. Leverage Custom Metadata for Dynamic Callouts
Want to avoid hardcoding endpoint URLs, headers, or tokens? Store them in Custom Metadata Types. This makes your code more flexible, especially when switching between dev, QA, and prod environments.
String endpoint = My_Endpoint_Config__mdt.getInstance('MyService').Endpoint__c;
req.setEndpoint(endpoint);
It's like using environment variables in the cloud - keep your code clean and your deployments safe.
10. Mock Callouts in Tests - Stop Skipping Coverage
You can't make real callouts in test methods. But many devs still skip testing callouts or cheat with Test.isRunningTest()
hacks. Don't do that. Use HttpCalloutMock properly.
@isTest
global class MockHttpResponseGenerator implements HttpCalloutMock {
global HTTPResponse respond(HTTPRequest req) {
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setBody('{"id":"123","status":"success"}');
res.setStatusCode(200);
return res;
}
}
Then use it in your test:
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
Test.startTest();
MyCalloutClass.doCallout();
Test.stopTest();
Now your tests are bulletproof and won't randomly fail on deployments.
Conclusion
REST API callouts can either be your greatest ally or your biggest headache as a Salesforce developer. But with the right habits, patterns, and tools, you can master integrations like a pro. From using Named Credentials to mocking callouts in tests, every trick in your toolbox saves you time, reduces bugs, and keeps your org scalable.
So the next time you're staring down a gnarly integration project, remember: it's not just about making the callout - it's about making it smart, secure, and sustainable.
Happy coding, and may your APIs always return 200s!