Understanding the 302 Found status code in Connected App callouts
As developers, architects, and admins we integrate with external systems through Connected Apps constantly, and those integrations usually mean HTTP callouts from Apex. Sooner or later one of them comes back as System.HttpResponse[Status=Found, StatusCode=302]. That status code signifies a redirect. It is not an outright error, but a 302 you do not handle will break the integration just as effectively and leave your data synchronization or process automation in limbo. This guide covers why it happens, how to diagnose it, and how to handle the redirect in Apex.
Why do we get a 302 Found?
The HTTP 302 Found status code is a standard redirection response. A server that receives a request for a resource that has temporarily moved sends back a 302 along with a Location header holding the URL the client should redirect to. The usual triggers:
- Load balancing. The endpoint you hit redirects you to whichever server is active.
- Temporary maintenance. The service sends traffic to a maintenance page or a different instance.
- Authentication and authorization flows. OAuth 2.0 in particular involves redirects to an authorization server and back to your application callback URL.
- API versioning or endpoint changes. Older endpoints get redirected to newer, preferred ones.
- Geo-location redirection. The service sends the user to a regional server based on their IP address.
With Connected Apps, the 302 usually turns up when the API endpoint you called redirects somewhere else for processing or data retrieval. The catch is that the default Apex HttpRequest and HttpResponse classes might not follow those redirects automatically. Your code receives the 302 itself, and then has to interpret it and act on it.
Diagnosing the 302 response in Apex
Before you fix anything, find out what the server actually told you. When a Connected App callout returns 302 Found, inspect the full HttpResponse object for the details.
Inspecting the HttpResponse object
Log or inspect the HttpResponse object thoroughly on every callout. For a 302, these are the fields that matter:
getStatusCode()will be302.getStatus()will beFound.getHeader('Location')holds the URL the server wants you to redirect to. If that header is missing, the302is either erroneous or the external service is handling it in some unexpected way.getBody()sometimes carries extra information, or an HTML page explaining the redirect.
Here is a simple Apex example that captures and inspects an HttpResponse:
public class ConnectedAppCalloutService {
public static void makeCalloutAndInspect() {
String endpointUrl = 'https://your.external.api.com/resource'; // Replace with your actual endpoint
String oauthToken = 'Bearer your_access_token'; // Replace with your actual token
HttpRequest req = new HttpRequest();
req.setEndpoint(endpointUrl);
req.setMethod('GET');
req.setHeader('Authorization', oauthToken);
req.setHeader('Accept', 'application/json');
Http http = new Http();
HttpResponse res = null;
try {
res = http.send(req);
System.debug('--- HTTP Response ---');
System.debug('Status Code: ' + res.getStatusCode());
System.debug('Status: ' + res.getStatus());
System.debug('Body: ' + res.getBody());
if (res.getStatusCode() == 302) {
String redirectUrl = res.getHeader('Location');
System.debug('Redirect URL: ' + redirectUrl);
if (redirectUrl != null) {
// Handle the redirect (details in the next section)
System.debug('Received a 302 Found. Further action needed.');
} else {
System.debug('Received a 302 Found but no Location header was provided.');
}
} else if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
System.debug('Callout successful!');
// Process successful response
} else {
System.debug('Callout failed with status code: ' + res.getStatusCode());
// Handle other errors
}
} catch (System.CalloutException e) {
System.debug('Callout exception occurred: ' + e.getMessage());
// Handle callout exceptions
}
}
}
When you see StatusCode: 302, the Redirect URL in your debug log is the clue you need for the next step.
Strategies for handling 302 redirects in Apex
Once you have identified the 302 and extracted the Location header, you have a few options.
1. Manually follow the redirect
The most straightforward approach. Take the 302, pull the new URL out of the Location header, and fire a new HttpRequest at it. Any headers the target needs, Authorization among them, have to be carried over yourself.
Steps:
- Make the initial callout.
- Check whether the
StatusCodeis302. - If it is, retrieve the
Locationheader. - Create a new
HttpRequestobject pointing at theLocationURL. - Copy the relevant headers (
Authorization,Content-Type) from the original request or context onto the new one. - Make the second callout with
http.send(redirectReq). - Process the response from the second callout.
Here is how that looks:
public class ConnectedAppCalloutService {
public static String makeCalloutWithRedirectHandling() {
String endpointUrl = 'https://your.external.api.com/initial_resource'; // Replace
String oauthToken = 'Bearer your_access_token'; // Replace
HttpRequest req = new HttpRequest();
req.setEndpoint(endpointUrl);
req.setMethod('GET');
req.setHeader('Authorization', oauthToken);
req.setHeader('Accept', 'application/json');
Http http = new Http();
HttpResponse res = null;
try {
res = http.send(req);
if (res.getStatusCode() == 302) {
System.debug('Received 302 Found. Attempting to follow redirect.');
String redirectUrl = res.getHeader('Location');
if (redirectUrl != null) {
HttpRequest redirectReq = new HttpRequest();
redirectReq.setEndpoint(redirectUrl);
redirectReq.setMethod('GET'); // Or POST, etc., depending on the redirect target
// Crucially, copy necessary headers
redirectReq.setHeader('Authorization', oauthToken);
redirectReq.setHeader('Accept', 'application/json');
// You might need to copy other headers depending on the API
System.debug('Making follow-up callout to: ' + redirectUrl);
HttpResponse finalRes = http.send(redirectReq);
if (finalRes.getStatusCode() >= 200 && finalRes.getStatusCode() < 300) {
System.debug('Redirect callout successful!');
return finalRes.getBody();
} else {
System.debug('Redirect callout failed with status: ' + finalRes.getStatusCode() + ' - ' + finalRes.getStatus());
return 'Error during redirect: ' + finalRes.getBody();
}
} else {
System.debug('302 Found, but no Location header provided.');
return 'Error: 302 Found with no Location header.';
}
} else if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
System.debug('Initial callout successful!');
return res.getBody();
} else {
System.debug('Initial callout failed with status: ' + res.getStatusCode() + ' - ' + res.getStatus());
return 'Error: ' + res.getBody();
}
} catch (System.CalloutException e) {
System.debug('Callout exception occurred: ' + e.getMessage());
return 'Exception: ' + e.getMessage();
}
}
}
Things to watch:
- Redirect loops. They are rarer in API integrations, but cap the number of redirects your Apex will follow.
- HTTP method. Make sure the method on the redirect callout matches what the target endpoint expects. Often it is
GET, though it could bePOSTif the original request was aPOSTand the redirect is meant to process the same data. - State management. If your callouts use session-based authentication, the session has to be maintained or re-established for the redirected request.
2. Using HttpRequest.setFollowRedirects(Boolean) (limited usefulness)
The Apex HttpRequest class has a setFollowRedirects(Boolean) method, which sounds like it should end the problem here. In practice its effectiveness against 302 responses from Connected Apps can be limited. Salesforce documentation on the method is not extensively detailed for all scenarios, and Apex might still need manual intervention for complex redirect chains or when specific headers have to be managed across the hop.
I would handle redirection manually for anything critical, because you get control and predictability. If the redirect is simple and direct and the target endpoint needs no special header manipulation, you could try switching this on. Test thoroughly.
// Example of attempting to use setFollowRedirects
// Note: This might NOT work as expected in all 302 scenarios for Connected Apps.
public class ConnectedAppCalloutService {
public static String makeCalloutWithAutoRedirect() {
String endpointUrl = 'https://your.external.api.com/resource'; // Replace
String oauthToken = 'Bearer your_access_token'; // Replace
HttpRequest req = new HttpRequest();
req.setEndpoint(endpointUrl);
req.setMethod('GET');
req.setHeader('Authorization', oauthToken);
req.setHeader('Accept', 'application/json');
req.setFollowRedirects(true); // Attempt to follow redirects automatically
Http http = new Http();
HttpResponse res = null;
try {
res = http.send(req);
if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
System.debug('Callout successful (potentially after redirects)!');
return res.getBody();
} else {
System.debug('Callout failed with status: ' + res.getStatusCode() + ' - ' + res.getStatus());
// If setFollowRedirects(true) didn't work, you might still get a 302 here.
// You'd then fall back to manual handling as in method 1.
if (res.getStatusCode() == 302) {
System.debug('setFollowRedirects(true) did not resolve the 302. Manual handling is required.');
}
return 'Error: ' + res.getBody();
}
} catch (System.CalloutException e) {
System.debug('Callout exception occurred: ' + e.getMessage());
return 'Exception: ' + e.getMessage();
}
}
}
3. Getting the OAuth 2.0 flow right
If the 302 belongs to an OAuth 2.0 authorization process, where the user is being redirected to an authorization server, check your Connected App and Apex callback handling.
- Callback URL. The
callbackUrlin your Apex callout or in the Connected App settings has to match exactly where the external OAuth provider is configured to send the user back after authorization. - State parameter. Use the
stateparameter in your OAuth requests. It is usually carried through redirects and it helps prevent CSRF attacks. Your Apex needs to process it correctly on return. - Grant types. Know which OAuth 2.0 grant type you are on (Authorization Code, JWT Bearer Token, Username-Password). Each has its own flow and its own redirect points.
Apex does not drive the browser redirects in an OAuth flow, but the callback endpoint you define will receive the response, and the external service may go through more internal redirects before the access token is exchanged. Write the callback logic to cope with that.
Best practices for handling redirects
- Handle errors on every callout. Wrap
http.send()intry-catchblocks forSystem.CalloutExceptionand check theHttpResponsestatus codes rather than assuming success. - Log everything. Request URL, method, headers, status code, response body, and any redirect URLs. This is what saves you at debugging time.
- Design for idempotency where you can, so the same request made twice has the same effect as making it once. It matters more with redirects, where it is easy to re-process data by accident.
- Keep URLs and tokens out of the code. Custom Settings, Custom Metadata Types, or Hierarchical Custom Settings hold endpoint URLs, OAuth credentials, and other integration parameters, which turns an endpoint change, including a redirect target, into a config edit.
- Write Apex unit tests that simulate
302responses, so you can prove the redirect handling works under various conditions without touching production. - Cap redirect depth. A counter in your manual handling prevents infinite loops and protects your callout limits.
Key takeaways
- A
System.HttpResponse[Status=Found, StatusCode=302]in a Salesforce Apex callout means the external service redirected you. - The
Locationheader in theHttpResponseis the important one; it holds the URL to redirect to. - Manually extracting the
LocationURL and making a subsequent Apex callout is the most reliable way to handle a302. - Pass the essential headers,
Authorizationabove all, to the redirected request. - Thorough logging, error handling, and unit testing are what make redirects manageable in production.
- If the
302is part of an authentication process, know the OAuth flow and check that callback URLs and parameters are configured correctly.
Treat the 302 Found as a branch you handle rather than a failure you hit, and integrations with external systems over Connected Apps get a lot less fragile.
Leave a Comment