How External Services Asynchronous Callbacks Work
External Services asynchronous operations are described in an OpenAPI 3.x compliant specification with a callback operation. Asynchronous operations are registered by the system as a special type of invokable action that allows for longer response times. In contrast, External Services synchronous operations time out after 120 seconds. With asynchronous operations, you use Apex to define the callback and the time out for the delayed asynchronous response.
Required Editions
| Available in: Lightning Experience |
| Available in: Enterprise, Performance, Unlimited, and Developer Editions |
Example API Specification With Callback Operation
This example API specification features a callback operation within a mortgage application process for the fictitious company "Acme Mortgages". Callback-related definitions are in bold. We'll return to this example in other topics in this section.
{
"openapi": "3.0.0",
"servers": [{
"url": "/"
}],
"info": {
"version": "1.0",
"title": "Acme Mortgages",
"description": "Acme Mortgages"
},
"paths": {
"/applications": {
"post": {
"operationId": "SubmitApplication",
"description": "Submit a new mortgage application",
"parameters": [{
"name": "callbackUrlForErrorCases",
"in": "query",
"schema": {
"type": "string"
}
}],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"applicant": {
"$ref": "#/components/schemas/Contact"
},
"callbackUrlForOutcomes": {
"type": "object",
"properties": {
"approved": {
"type": "string"
},
"rejected": {
"type": "string"
}
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "Mortgage loan application submission initial response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"applicationNumber": {
"type": "string"
}
}
}
}
}
}
},
"callbacks": {
"applicationOutcomeApproved": {
"$ref": "#/components/callbacks/ApplicationApproved"
},
"applicationOutcomeRejected": {
"$ref": "#/components/callbacks/ApplicationRejected"
},
"applicationError": {
"{$request.query.callbackUrlForErrorCases}": {
"post": {
"parameters": [{
"in": "header",
"name": "applicationNumber",
"schema": {
"type": "string"
}
}],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MortgageApplicationError"
}
}
}
},
"responses": {
"200": {
"description": "Mortgage application callback error accepted"
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"MortgageApplication": {
"required": [
"applicationNumber",
"status"
],
"properties": {
"applicationNumber": {
"type": "string"
},
"status": {
"description": "One of pending, approved, rejected",
"type": "string"
},
"approvedAmount": {
"type": "number"
}
}
},
"Contact": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"address": {
"type": "string"
}
}
},
"MortgageApplicationError": {
"properties": {
"errorMessage": {
"type": "string"
},
"applicationNumber": {
"type": "string"
}
}
}
},
"callbacks": {
"ApplicationApproved": {
"{$request.body#/callbackUrlForOutcomes/approved}": {
"post": {
"description": "Application has been approved.",
"operationId": "approvedCallback",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MortgageApplication"
}
}
}
},
"responses": {
"200": {
"description": "Approved application callback result has been retrieved successfully."
}
}
}
}
},
"ApplicationRejected": {
"{$request.body#/callbackUrlForOutcomes/rejected}": {
"post": {
"description": "Application is rejected",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MortgageApplicationError"
}
}
}
},
"responses": {
"200": {
"description": "Rejected application callback result has been retrieved successfully."
}
}
}
}
}
}
}
}
Example Apex Interface With Asynchronous Operations
When registered, the AcmeMortgages external
service is automatically rendered in Apex by External Services. The Apex interface
for AcmeMortgages appears in the Apex Classes
page in Setup as
shown
here. Additional content that defines the asynchronous operations is in bold. Not
shown are the dynamic Apex objects that represent schema object data types that are
the same as invocable actions.
Each callback is owned by its parent asynchronous operation. A unique, read-only callback URL is determined by Salesforce (on the first callout).
global class AcmeMortgages {
// Acme Mortgages SubmitApplication asynchronous operation
global SubmitApplication_Response SubmitApplication(
SubmitApplication_Request input,
SubmitApplication_Callback callback,
DateTime callbackTimeout
) {...}
global class SubmitApplication_Request {
global AcmeMortgages_SubmitApplication_IN_body body {get; set;}
// [REQUEST] Callback URL for asynchronous operation: callbackUrlForErrorCases
global String callbackUrlForErrorCases {get;}
}
// Synchronous response - here getting the application docket number
global class SubmitApplication_Response {
global Integer responseCode {get; set;}
global AcmeMortgages_SubmitApplication_OUT_201 Code201 {get; set;}
// Get invocation ID for this asynchronous response to query callback status
global String invocationId {get;}
}
global class SubmitApplication_ResponseException {
global Integer responseCode {get; set;}
global String defaultResponse {get; set;}
}
// Asynchronous response callback handler for SubmitApplication.
// Each method corresponds to the schema's declared callback key.
// Default implementations throws an exception alerting that a non-implemented callback was called.
global virtual class SubmitApplication_Callback {
global virtual void applicationOutcomeApproved(
List<SubmitApplication_applicationOutcomeApproved_Callback> callbacks) {
throw new SubmitApplication_ResponseException(404,
'Callback not handled: applicationOutcomeApproved');
}
global virtual void applicationOutcomeRejected(List<SubmitApplication_applicationOutcomeRejected_Callback> callbacks) {...}
global virtual void applicationError(List<SubmitApplication_applicationError_Callback> callbacks) {...}
}
// Asynchronous callback response payloads for callback applicationOutcomeApproved
global class SubmitApplication_applicationOutcomeApproved_Callback {
global SubmitApplication_Request request {get; set;}
global DateTime submitTime {get; set;}
global DateTime callbackTimeout {get; set;}
global CallbackStatus callbackStatus {get; set;}
global SubmitApplication_applicationOutcomeApproved_CallbackResponse response {get; set;}
}
global class SubmitApplication_applicationOutcomeApproved_CallbackResponse {
global AcmeMortgages_MortgageApplication body {get; set;}
}
global class SubmitApplication_applicationOutcomeRejected_Callback {...}
global class SubmitApplication_applicationOutcomeRejected_CallbackResponse {...}
global class SubmitApplication_applicationError_Callback {...}
global class SubmitApplication_applicationError_CallbackResponse {...}
}
Example Callback Data Flow
In this example, a Salesforce Apex developer uses External Services to register a bank's mortgage application API specification. The spec contains callback operations, as delays of up to one day between application submission and acceptance are expected.
The Apex developer writes an Apex client with a callback handler. The Apex client specifies that after Salesforce receives the delayed response from the external system, Salesforce uses the resultant list of names and other mortgage application information to automatically create Contacts.
This sequence diagram demonstrates the functional data flow between the developer's Apex code ("Apex Client / Handler" in the diagram), Salesforce, and the bank's mortgage application API endpoint ("External Webservice" in the diagram).
There are two responses from the external web service. The first is synchronous and is the typical, initial ACK response. The second is the asynchronous, delayed response (technically a request) that includes the result payload in the request body.
First, the Apex developer sends an HTTP request (a callout) to the bank server (an external service) with the Apex client.
The external web service immediately sends back an HTTP response acknowledging receipt of the request. This first acknowledgment times out after 120 seconds. The Salesforce developer monitors the status of the request by using the Background Operations page or the Apex Debug log.
The bank starts its internal process to ingest the mortgage application, approve or deny it, and eventually prepare a final result (in the diagram as "Async Delay").
Within one day, the bank sends the completed result back to the Apex developer as an HTTP request. The result data (approved amount, status, and so on) for the mortgage application is contained inside the body of the request. Salesforce receives the result and processes it according to the Apex callback handler implementation.
Salesforce returns an HTTP response acknowledgment to the external web service. If Salesforce processes the data successfully—in this case, to create contacts—then it sends a response to the external webservice with a 200 status code and a success message.
If Salesforce encounters an error, it sends a 404, 408, or a 500 status code to the external webservice with a generic error message. The Apex developer can see more error message details by using the Background Operations page or with the Apex Debug log.

