fetch() API (with CSP Trusted Sites enabled) or securely through an Apex callout using Named Credentials.
Connecting Salesforce applications with external web services is a core requirement in logistics, fleet management, and finance. For instance, querying a toll calculation service allows transportation teams to estimate route costs dynamically from an Account or Work Order page. Below, we break down how to configure network permissions and execute external API requests cleanly from an LWC component.
1. Choosing the Right Integration Pattern: Client-Side vs. Apex Bridge
When calling external endpoints in Salesforce, developers have two primary architectures:
- Client-Side (
fetch()in LWC): The browser makes a direct HTTP call to the external server. This requires adding the domain to CSP Trusted Sites in Setup. It works well for public or non-sensitive APIs where credentials are not exposed. - Server-Side (Apex Callout with Named Credentials): The LWC calls an Apex method, and Salesforce's servers make the HTTP request. This is the industry-standard approach because API tokens, secrets, and auth headers remain securely hidden on the server.
- Client Method: Native JavaScript
fetch(url, options). - Salesforce Security Requirement: Domain whitelisted in Setup > CSP Trusted Sites (
connect-src). - Recommended Enterprise Pattern: Apex controller + Named Credentials to secure private keys and prevent CORS issues.
- UI Feedback: Reactive loading spinners (
<lightning-spinner>) and toast alerts (ShowToastEvent).
2. Step-by-Step Implementation: Building the LWC Integration
The following example builds a responsive component that triggers an external API request, displays a loading indicator during transit, and handles the JSON response.
tollingCalculator.html)
<template>
<lightning-card title="Tolling & Route Cost Calculator" icon-name="standard:service_territory_location">
<div class="slds-p-around_medium">
<!-- Loading Spinner -->
<template lwc:if={isLoading}>
<lightning-spinner alternative-text="Loading toll data..." size="small"></lightning-spinner>
</template>
<p class="slds-m-bottom_small">
Click below to calculate real-time toll charges for this transport route.
</p>
<lightning-button
label="Fetch Toll Estimate"
variant="brand"
icon-name="utility:moneybag"
onclick={handleFetchTollData}
disabled={isLoading}>
</lightning-button>
<!-- Results Section -->
<template lwc:if={tollResult}>
<div class="slds-box slds-theme_shade slds-m-top_medium">
<p><strong>Route Status:</strong> {tollResult.status}</p>
<p><strong>Estimated Cost:</strong> ${tollResult.cost}</p>
<p><strong>Toll Booths Passed:</strong> {tollResult.boothCount}</p>
</div>
</template>
</div>
</lightning-card>
</template>
tollingCalculator.js)
import { LightningElement, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class TollingCalculator extends LightningElement {
@track tollResult;
isLoading = false;
async handleFetchTollData() {
this.isLoading = true;
this.tollResult = null;
const apiUrl = 'https://api.tollingprovider.com/v1/calculate';
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
try {
const response = await fetch(apiUrl, requestOptions);
if (!response.ok) {
throw new Error(`HTTP Error ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Process and bind response data
this.tollResult = {
status: data.status || 'Active',
cost: data.estimatedToll || 0.00,
boothCount: data.tollCount || 0
};
this.showToast('Success', 'Toll calculations retrieved successfully.', 'success');
} catch (error) {
console.error('API Invocation Failed:', error);
this.showToast('Request Failed', error.message || 'Unable to connect to tolling service.', 'error');
} finally {
this.isLoading = false;
}
}
showToast(title, message, variant) {
this.dispatchEvent(
new ShowToastEvent({
title: title,
message: message,
variant: variant
})
);
}
}
3. Prerequisites & Salesforce Setup Configuration
To enable direct browser callouts from Lightning components, you must configure network policies in Setup:
- Enable CSP Trusted Sites: In Setup, search for CSP Trusted Sites and add the API base URL (e.g.,
https://api.tollingprovider.com). Check theconnect-srcdirective to allow AJAX/Fetch calls. - Configure Remote Site Settings: If routing through Apex instead of client-side JavaScript, add the URL under Remote Site Settings or configure a Named Credential.
4. Common Security Traps & Best Practices
Never hardcode private Bearer tokens, passwords, or confidential API keys directly inside
headers in a client-side .js file. Anyone with browser Developer Tools can inspect your JavaScript bundle and steal your credentials. For protected APIs requiring secret authentication, route calls through an Apex controller backed by Named Credentials.
- Manage User Experience with Spinners: Always toggle a loading spinner (
isLoading = true) and disable trigger buttons during asynchronous network calls to prevent duplicate submissions. - Always Check
response.ok: The browser'sfetch()promise does not reject on HTTP error statuses (like 404 or 500); it only rejects on network failures. Always evaluateresponse.okbefore parsing JSON. - Sanitize URL Parameters: Use
encodeURIComponent()when appending dynamic user inputs (such as postal codes or vehicle classes) to query strings.
Summary
Integrating third-party REST services into Salesforce Lightning Web Components delivers powerful external data directly into your CRM workflows. By understanding the balance between client-side fetch() speed and server-side Apex security, developers can build robust, production-grade external integrations that protect sensitive credentials and provide seamless user experiences.