Callback¶
Wait for external systems¶
A callback suspends a durable function and waits for an external system to provide input. When you create a callback, the SDK checkpoints the operation and returns a unique callback ID. After you send the callback ID to an external system, the SDK suspends the durable function and terminates the current invocation. The function does not incur compute charges while suspended.
External systems send callback results using the
SendDurableExecutionCallbackSuccess or SendDurableExecutionCallbackFailure Lambda
APIs.
When the external system calls back with the ID, the backend starts a new invocation to resume your function from where it suspended.
Use callbacks when you need to pause execution until a human approves a request, or a
payment processor confirms a transaction, or any other external system completes work
asynchronously. The SDK provides two ways to work with callbacks, either create a
callback and manage the external system notification and wait yourself, or use
waitForCallback to handle submission and waiting in one operation.
The SDK provides two operations for working with callbacks.
Create Callback¶
Create a callback and return a callback ID along with a handle to wait for the result. The wait does not consume compute.
You send the callback ID to an external system. The external system uses that ID to send callback results using the Lambda API.
import {
DurableContext,
withDurableExecution,
} from "@aws/durable-execution-sdk-js";
export const handler = withDurableExecution(
async (event: any, context: DurableContext) => {
const [callbackPromise, callbackId] = await context.createCallback(
"wait-for-approval",
);
// Send callbackId to the external system that will resume this function.
await sendApprovalRequest(callbackId, event.requestId);
// Execution suspends here until the external system calls back.
const result = await callbackPromise;
return { approved: true, result };
},
);
from typing import Any
from aws_durable_execution_sdk_python import DurableContext, durable_execution
@durable_execution
def handler(event: Any, context: DurableContext) -> dict:
callback = context.create_callback(name="wait-for-approval")
# Send callback.callback_id to the external system that will resume this function.
send_approval_request(callback.callback_id, event["request_id"])
# Execution suspends here until the external system calls back.
result = callback.result()
return {"approved": True, "result": result}
import software.amazon.lambda.durable.DurableCallbackFuture;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;
public class BasicCallbackExample extends DurableHandler<Object, String> {
@Override
public String handleRequest(Object input, DurableContext context) {
DurableCallbackFuture<String> callback =
context.createCallback("wait-for-approval", String.class);
// Send callback.callbackId() to the external system that will resume this function.
sendApprovalRequest(callback.callbackId());
// Execution suspends here until the external system calls back.
return callback.get();
}
}
Wait for Callback¶
Wait for Callback combines callback creation, submission, and waiting for the result in one operation.
Internally the SDK creates a child context containing a create callback operation followed by a step that runs the submitter function and then waits for the result.
import {
DurableContext,
withDurableExecution,
} from "@aws/durable-execution-sdk-js";
export const handler = withDurableExecution(
async (event: any, context: DurableContext) => {
const result = await context.waitForCallback(
"wait-for-approval",
async (callbackId) => {
await sendApprovalRequest(callbackId, event.requestId);
},
);
return { approved: true, result };
},
);
from typing import Any
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.types import WaitForCallbackContext
@durable_execution
def handler(event: Any, context: DurableContext) -> dict:
def submit(callback_id: str, ctx: WaitForCallbackContext) -> None:
send_approval_request(callback_id, event["request_id"])
result = context.wait_for_callback(submitter=submit, name="wait-for-approval")
return {"approved": True, "result": result}
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;
public class WaitForCallbackExample extends DurableHandler<Object, String> {
@Override
public String handleRequest(Object input, DurableContext context) {
return context.waitForCallback(
"wait-for-approval",
String.class,
(callbackId, stepCtx) -> sendApprovalRequest(callbackId));
}
}
Callback lifecycle¶
A callback spans multiple invocations within a single execution.
sequenceDiagram
participant Inv1 as Invocation 1
participant Backend as Durable Backend
participant Ext as External System
participant Inv2 as Invocation 2
note over Inv1, Inv2: Single Execution
Inv1->>Backend: createCallback → checkpoint & get callback ID
Inv1->>Ext: Send callback ID
Inv1->>Backend: Suspend (invocation terminates, no compute)
Ext->>Ext: Do async work
Ext->>Backend: SendDurableExecutionCallbackSuccess/Failure
Backend->>Inv2: Start new invocation, resume from checkpoint
Inv2->>Inv2: Continue execution with callback result
- Function The durable Lambda function. This contains your code.
- Execution The complete end-to-end lifecycle of an AWS Lambda durable function
- Invocation A single invocation of the function during the over-all execution. The invocation terminates at a callback wait. The backend re-invokes the function when the callback resolves, and replays to resume at the callback wait point with the result.
Method signatures¶
createCallback¶
// Named overload
createCallback<TOutput = string>(
name: string | undefined,
config?: CreateCallbackConfig<TOutput>,
): DurablePromise<[DurablePromise<TOutput>, string]>;
// Anonymous overload
createCallback<TOutput = string>(
config?: CreateCallbackConfig<TOutput>,
): DurablePromise<[DurablePromise<TOutput>, string]>;
Parameters:
name(optional) A name for the callback. Passundefinedto omit.config(optional) ACreateCallbackConfig<TOutput>object.
Returns: DurablePromise<[DurablePromise<TOutput>, string]>. Destructure with
await to get [callbackPromise, callbackId]. Await callbackPromise to suspend until
the external system calls back.
Throws: CallbackError if the callback fails, times out, or the external system
reports failure. The error is thrown by callbackPromise, not by createCallback
itself.
def create_callback(
self,
name: str | None = None,
config: CallbackConfig | None = None,
) -> Callback: ...
Parameters:
name(optional) A name for the callback.config(optional) ACallbackConfigobject.
Returns: A Callback object. Access callback.callback_id to get the ID to send to
the external system. Call callback.result() to suspend until the external system calls
back.
Raises: CallbackError from callback.result() if the callback fails or times out.
// With Class<T>
<T> DurableCallbackFuture<T> createCallback(String name, Class<T> resultType)
<T> DurableCallbackFuture<T> createCallback(String name, Class<T> resultType, CallbackConfig config)
// With TypeToken<T> for generic result types
<T> DurableCallbackFuture<T> createCallback(String name, TypeToken<T> resultType)
<T> DurableCallbackFuture<T> createCallback(String name, TypeToken<T> resultType, CallbackConfig config)
Parameters:
name(required) A name for the callback.resultTypeTheClass<T>orTypeToken<T>for deserialization.config(optional) ACallbackConfigobject.
Returns: DurableCallbackFuture<T>. Call callback.callbackId() to get the ID to
send to the external system. Call callback.get() to suspend until the external system
calls back.
Throws: CallbackFailedException if the external system reports failure.
CallbackTimeoutException if the callback times out.
Callback Handle¶
The object returned by createCallback.
[callbackPromise, callbackId] — a tuple destructured from the awaited result.
callbackId(string) The unique ID to send to the external system.callbackPromise(DurablePromise<TOutput>) Await to suspend until the external system calls back.
callback_idThe unique ID to send to the external system.result()Suspends until the external system calls back. RaisesCallbackErrorif the callback fails or times out.
CallbackConfig¶
interface CreateCallbackConfig<TOutput = string> {
timeout?: Duration;
heartbeatTimeout?: Duration;
serdes?: Omit<Serdes<TOutput>, "serialize">;
}
Parameters:
timeout(optional) Maximum time to wait for the callback result. See Duration for how to specify durations.heartbeatTimeout(optional) Maximum time between heartbeat signals from the external system. If the external system does not send a heartbeat within this interval, the callback fails.serdes(optional) Custom deserializer for the callback result. See Serialization.
@dataclass(frozen=True)
class CallbackConfig:
timeout: Duration = Duration()
heartbeat_timeout: Duration = Duration()
serdes: SerDes | None = None
Parameters:
timeout(optional) Maximum time to wait for the callback result. See Duration for how to specify durations.heartbeat_timeout(optional) Maximum time between heartbeat signals from the external system. If the external system does not send a heartbeat within this interval, the callback fails.serdes(optional) CustomSerDesfor the callback result. See Serialization.
CallbackConfig.builder()
.timeout(Duration) // optional
.heartbeatTimeout(Duration) // optional
.serDes(SerDes) // optional
.build()
Parameters:
timeout(optional) Maximum time to wait for the callback result. Usesjava.time.Duration.heartbeatTimeout(optional) Maximum time between heartbeat signals from the external system. If the external system does not send a heartbeat within this interval, the callback fails.serDes(optional) CustomSerDesfor the callback result. See Serialization.
waitForCallback¶
waitForCallback is a composite operation that combines createCallback with a step
that runs your submitter function. The submitter receives the callback ID and is
responsible for sending it to the external system. If the submitter fails, the SDK
retries it according to the step's retry strategy.
The external system sends callback results using the
SendDurableExecutionCallbackSuccess or SendDurableExecutionCallbackFailure Lambda
APIs.
Use waitForCallback when you want the SDK to handle the retry logic for submitting the
callback ID rather than coding it yourself.
// Named overload
waitForCallback<TOutput = string>(
name: string | undefined,
submitter: (callbackId: string, context: WaitForCallbackContext) => Promise<void>,
config?: WaitForCallbackConfig<TOutput>,
): DurablePromise<TOutput>;
// Anonymous overload
waitForCallback<TOutput = string>(
submitter: (callbackId: string, context: WaitForCallbackContext) => Promise<void>,
config?: WaitForCallbackConfig<TOutput>,
): DurablePromise<TOutput>;
Parameters:
name(optional) A name for the operation. Passundefinedto omit.submitterA function that receives the callback ID and aWaitForCallbackContext, and submits the ID to the external system.config(optional) AWaitForCallbackConfig<TOutput>object.
Returns: DurablePromise<TOutput>. Await to get the callback result.
Throws: CallbackError if the callback fails, times out, or the external system
reports failure.
def wait_for_callback(
self,
submitter: Callable[[str, WaitForCallbackContext], None],
name: str | None = None,
config: WaitForCallbackConfig | None = None,
) -> Any: ...
Parameters:
submitterA callable that receives the callback ID and aWaitForCallbackContext, and submits the ID to the external system.name(optional) A name for the operation.config(optional) AWaitForCallbackConfigobject.
Returns: The callback result.
Raises: CallbackError if the callback fails or times out.
// sync
<T> T waitForCallback(String name, Class<T> resultType, BiConsumer<String, StepContext> func)
<T> T waitForCallback(String name, Class<T> resultType, BiConsumer<String, StepContext> func, WaitForCallbackConfig config)
// async
<T> DurableFuture<T> waitForCallbackAsync(String name, Class<T> resultType, BiConsumer<String, StepContext> func)
<T> DurableFuture<T> waitForCallbackAsync(String name, Class<T> resultType, BiConsumer<String, StepContext> func, WaitForCallbackConfig config)
Parameters:
name(required) A nullable name for the operation.resultTypeTheClass<T>orTypeToken<T>for deserialization.funcABiConsumer<String, StepContext>that receives the callback ID and submits it to the external system.config(optional) AWaitForCallbackConfigobject.
Returns: T (sync) or DurableFuture<T> (async via waitForCallbackAsync()).
Throws: CallbackFailedException if the external system reports failure.
CallbackTimeoutException if the callback times out. CallbackSubmitterException if
the submitter step fails after exhausting retries.
WaitForCallbackConfig¶
WaitForCallbackConfig extends CallbackConfig with retry configuration for the
submitter step.
interface WaitForCallbackConfig<TOutput = string> {
timeout?: Duration;
heartbeatTimeout?: Duration;
retryStrategy?: (error: Error, attemptCount: number) => RetryDecision;
serdes?: Omit<Serdes<TOutput>, "serialize">;
}
retryStrategy(optional) A function returning aRetryDecisionfor the submitter step. See Retry strategies.
@dataclass(frozen=True)
class WaitForCallbackConfig(CallbackConfig):
retry_strategy: Callable[[Exception, int], RetryDecision] | None = None
retry_strategy(optional) A callable returning aRetryDecisionfor the submitter step. See Retry strategies.
WaitForCallbackConfig.builder()
.stepConfig(StepConfig) // optional
.callbackConfig(CallbackConfig) // optional
.build()
stepConfig(optional) AStepConfigfor the submitter step, including retry strategy. See Retry strategies.callbackConfig(optional) ACallbackConfigfor the callback wait.
Timeout configuration¶
Set a timeout to limit the duration the function waits for the external system. If the
timeout expires before the external system calls back, the SDK raises a
CallbackTimeoutException (Java) or CallbackError (TypeScript, Python) from the
result call.
The timeout is bound by the ExecutionTimeout you set on the
DurableConfig.
The ExecutionTimeout applies to the entire durable execution, not individual function
invocations.
The durable execution terminates with a timeout error if the callback does not receive a
response within the ExecutionTimeout.
Since the durable function suspends and does not consume compute during the wait, the callback timeout can exceed the Lambda function invocation timeout.
Set a heartbeatTimeout when the external system can send periodic heartbeat signals
during long-running work. If the external system stops sending heartbeats within the
interval, the callback fails before the main timeout expires. Heartbeat timeouts can
help detect stalled external systems sooner.
import {
DurableContext,
withDurableExecution,
} from "@aws/durable-execution-sdk-js";
export const handler = withDurableExecution(
async (event: any, context: DurableContext) => {
const [callbackPromise, callbackId] = await context.createCallback(
"wait-for-payment",
{
timeout: { hours: 24 },
heartbeatTimeout: { minutes: 30 },
},
);
await submitPaymentRequest(callbackId, event.amount);
return await callbackPromise;
},
);
from typing import Any
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.config import CallbackConfig, Duration
@durable_execution
def handler(event: Any, context: DurableContext) -> dict:
config = CallbackConfig(
timeout=Duration.from_hours(24),
heartbeat_timeout=Duration.from_minutes(30),
)
callback = context.create_callback(name="wait-for-payment", config=config)
submit_payment_request(callback.callback_id, event["amount"])
return callback.result()
import java.time.Duration;
import software.amazon.lambda.durable.DurableCallbackFuture;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;
import software.amazon.lambda.durable.config.CallbackConfig;
public class CallbackConfigExample extends DurableHandler<Object, String> {
@Override
public String handleRequest(Object input, DurableContext context) {
CallbackConfig config = CallbackConfig.builder()
.timeout(Duration.ofHours(24))
.heartbeatTimeout(Duration.ofMinutes(30))
.build();
DurableCallbackFuture<String> callback =
context.createCallback("wait-for-payment", String.class, config);
submitPaymentRequest(callback.callbackId());
return callback.get();
}
}
Naming callbacks¶
Name callbacks to make them easier to identify in logs and tests. Use a name that describes what the callback is waiting for.
The name is the first argument. Pass undefined to omit it.
Pass name as a keyword argument.
The name is always the first argument. Pass null to omit it.
Send callback results¶
External systems send results back using the
SendDurableExecutionCallbackSuccess
or
SendDurableExecutionCallbackFailure
Lambda APIs. The callback ID returned from createCallback or passed into
waitForCallback is the key that routes the result back to the waiting function. For
long-running callbacks, external systems can send periodic
SendDurableExecutionCallbackHeartbeat
signals to prevent the heartbeatTimeout from expiring.
Most production integrations call these APIs using the AWS SDK for your programming language. You can also send callbacks from the Console, the AWS CLI, or the SAM CLI. In the Console, open the Durable Executions tab, select the running execution, find the callback under Durable Operations, and use the Actions drop-down to send a response.
AWS CLI¶
The AWS CLI sends callbacks directly to the Lambda backend and requires AWS credentials.
Success:
aws lambda send-durable-execution-callback-success \
--callback-id <callback-id> \
--cli-binary-format raw-in-base64-out \
--result '{"status":"approved"}'
--result is a blob. AWS CLI v2 expects blobs as base64 by default, so pass
--cli-binary-format raw-in-base64-out to send a plain string inline, or use
fileb://result.json to read from a file.
Failure:
aws lambda send-durable-execution-callback-failure \
--callback-id <callback-id> \
--error ErrorType=PaymentDeclined,ErrorMessage="Insufficient funds"
The --error value accepts shorthand syntax (Key=value,...) or JSON, and supports
ErrorType, ErrorMessage, ErrorData, and StackTrace.
SAM CLI¶
The SAM CLI provides two subcommands depending on where your function is running.
sam local callback sends the callback to the SAM local runner. Use this during local
development when your function is running via sam local start-lambda. It does not
require AWS credentials.
sam remote callback sends the callback to the actual Lambda backend. Use this when
your function is deployed to AWS. It requires AWS credentials.
Success:
# local runner
sam local callback succeed <callback-id> --result '{"status":"approved"}'
# Lambda backend
sam remote callback succeed <callback-id> --result '{"status":"approved"}'
Failure:
# local runner
sam local callback fail <callback-id> \
--error-type "PaymentDeclined" \
--error-message "Insufficient funds"
# Lambda backend
sam remote callback fail <callback-id> \
--error-type "PaymentDeclined" \
--error-message "Insufficient funds"
See also¶
- Wait for Condition Poll for a status change, rather than waiting for the callback
- Wait Time-based durable waits.
- Error handling