Modern enterprise applications frequently need to test, query, or sync with third-party web services. By combining Lightning Web Components with Apex HTTP classes, you can build a flexible API client inside Salesforce to send GET, POST, or PUT requests to any configured external endpoint on demand.
Prerequisites
- A Salesforce Developer Edition, Sandbox, or Scratch Org.
- Salesforce CLI (
sf) installed and authenticated. - Basic knowledge of REST APIs, JSON formatting, and Apex HTTP classes.
- Target external URLs registered in Remote Site Settings or configured via Named Credentials.
Step 1: Set Up Remote Site Settings or Named Credentials
Step 2: Create the Apex Controller
Create an Apex class named HttpCalloutController.cls. This controller receives the dynamic request parameters, constructs the HttpRequest, and handles status codes and error responses safely:
public with sharing class HttpCalloutController {
@AuraEnabled
public static String makeHttpRequest(String endpoint, String method, String headersJson, String payload) {
try {
if (String.isBlank(endpoint)) {
throw new AuraHandledException('Endpoint URL cannot be empty.');
}
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(endpoint.trim());
request.setMethod(String.isNotBlank(method) ? method.toUpperCase() : 'GET');
request.setTimeout(120000); // Max timeout: 120 seconds
// Parse and attach dynamic JSON headers if provided
if (String.isNotBlank(headersJson)) {
Map<String, Object> headersMap = (Map<String, Object>) JSON.deserializeUntyped(headersJson);
for (String key : headersMap.keySet()) {
request.setHeader(key, String.valueOf(headersMap.get(key)));
}
}
// Set default Content-Type header if not provided
if (request.getHeader('Content-Type') == null) {
request.setHeader('Content-Type', 'application/json');
}
// Attach request body for POST, PUT, or PATCH
if (String.isNotBlank(payload) && request.getMethod() != 'GET') {
request.setBody(payload);
}
HttpResponse response = http.send(request);
// Package response details
Map<String, Object> result = new Map<String, Object>{
'statusCode' => response.getStatusCode(),
'status' => response.getStatus(),
'body' => response.getBody()
};
return JSON.serializePretty(result);
} catch (Exception ex) {
throw new AuraHandledException('Callout failed: ' + ex.getMessage());
}
}
}
Step 3: Build the LWC Template
Create an LWC named httpCalloutLWC. Open httpCalloutLWC.html and build the input form with SLDS styling, a method dropdown, and a formatted response preview area:
<template>
<lightning-card title="Dynamic HTTP Callout Tester" icon-name="utility:connected_apps">
<div class="slds-p-around_medium">
<template lwc:if={isLoading}>
<lightning-spinner alternative-text="Sending request..." size="small"></lightning-spinner>
</template>
<div class="slds-grid slds-gutters slds-m-bottom_small">
<div class="slds-col slds-size_1-of-4">
<lightning-combobox
name="method"
label="HTTP Method"
value={method}
options={methodOptions}
onchange={handleInputChange}>
</lightning-combobox>
</div>
<div class="slds-col slds-size_3-of-4">
<lightning-input
name="endpoint"
label="Endpoint URL"
placeholder="https://jsonplaceholder.typicode.com/posts"
value={endpoint}
onchange={handleInputChange}>
</lightning-input>
</div>
</div>
<div class="slds-m-bottom_small">
<lightning-textarea
name="headers"
label="Headers (JSON format)"
placeholder='{"Authorization": "Bearer token", "Accept": "application/json"}'
value={headers}
onchange={handleInputChange}>
</lightning-textarea>
</div>
<div class="slds-m-bottom_medium">
<lightning-textarea
name="payload"
label="Request Body (Payload)"
placeholder='{"title": "Test Title", "body": "Sample content", "userId": 1}'
value={payload}
onchange={handleInputChange}>
</lightning-textarea>
</div>
<lightning-button
variant="brand"
label="Send HTTP Request"
onclick={handleMakeRequest}
disabled={isLoading}>
</lightning-button>
<template lwc:if={response}>
<div class="slds-m-top_large">
<h3 class="slds-text-heading_small slds-m-bottom_x-small">Response Output:</h3>
<pre class="ap-pre"><code>{response}</code></pre>
</div>
</template>
</div>
</lightning-card>
</template>
Step 4: Implement the JavaScript Controller Logic
In httpCalloutLWC.js, manage user input state, call the Apex method imperatively, and catch client/server exceptions:
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import makeHttpRequest from '@salesforce/apex/HttpCalloutController.makeHttpRequest';
export default class HttpCalloutLWC extends LightningElement {
endpoint = 'https://jsonplaceholder.typicode.com/posts/1';
method = 'GET';
headers = '';
payload = '';
response = '';
isLoading = false;
get methodOptions() {
return [
{ label: 'GET', value: 'GET' },
{ label: 'POST', value: 'POST' },
{ label: 'PUT', value: 'PUT' },
{ label: 'DELETE', value: 'DELETE' }
];
}
handleInputChange(event) {
const { name, value } = event.target;
this[name] = value;
}
async handleMakeRequest() {
if (!this.endpoint) {
this.showToast('Validation Error', 'Please enter a valid Endpoint URL.', 'warning');
return;
}
this.isLoading = true;
this.response = '';
try {
const rawResponse = await makeHttpRequest({
endpoint: this.endpoint,
method: this.method,
headersJson: this.headers,
payload: this.payload
});
this.response = rawResponse;
this.showToast('Success', 'HTTP Callout executed successfully.', 'success');
} catch (error) {
this.response = error?.body?.message || JSON.stringify(error);
this.showToast('Callout Error', error?.body?.message || 'Failed to complete callout.', 'error');
} finally {
this.isLoading = false;
}
}
showToast(title, message, variant) {
this.dispatchEvent(new ShowToastEvent({ title, message, variant }));
}
}
- Imperative Apex: HTTP callouts cannot use
@wirebecause callouts have side effects and mutate external state. - Heap Limits: Ensure response payloads do not exceed the synchronous Apex 6 MB transaction heap size limit.
- CORS & Security: Browser-based
fetch()in LWC is subject to Lightning Locker/LWS CORS rules; routing callouts through Apex ensures reliable server-to-server connectivity.
Step 5: Expose and Deploy to Lightning Pages
- Ensure
<isExposed>true</isExposed>is set inhttpCalloutLWC.js-meta.xmlwith targets forlightning__AppPageandlightning__RecordPage. - Deploy component and controller:
sf project deploy start. - Open Lightning App Builder in Setup and place the component on your testing page.
- Test using a public test REST endpoint like
https://jsonplaceholder.typicode.com/posts/1.