Callbacks
Callbacks let you receive delivery results without waiting for the HTTP request to complete. QStash calls your callback URL with the response after delivering the message.
Why Use Callbacks?
Serverless functions have execution time limits. Callbacks allow you to:
- Publish long-running tasks without blocking
- Receive delivery confirmation asynchronously
- Handle failures separately with failure callbacks
You can use callbacks individually or together:
await client.publishJSON({
url: "https://api.example.com/webhook",
body: { order: "12345" },
callback: "https://api.example.com/callback",
failureCallback: "https://api.example.com/failure",
});callback
Called after each delivery attempt (success or failure):
The callback is invoked after every retry attempt until the destination returns a 2XX status or retries are exhausted. Check retried === maxRetries in the callback body to detect final failure.
failureCallback
Called only when all retries are exhausted:
Use this as a serverless alternative to polling the DLQ. See DLQ for more options.
Callback Payload
Success Callback Body
{
"status": 200,
"header": { "content-type": ["application/json"] },
"body": "YmFzZTY0IGVuY29kZWQgcm9keQ==",
"retried": 2,
"maxRetries": 3,
"sourceMessageId": "msg_xxx",
"topicName": "myTopic",
"endpointName": "myEndpoint",
"url": "https://api.example.com/webhook",
"method": "POST",
"sourceHeader": { "content-type": "application/json" },
"sourceBody": "YmFzZTY0IGVuY29kZWQgcm9keQ==",
"notBefore": 1701198458025,
"createdAt": 1701198447054,
"scheduleId": "scd_xxx",
"callerIP": "178.247.74.179"
}Failure Callback Body
{
"status": 500,
"header": { "content-type": ["text/plain"] },
"body": "RXJyb3IgbWVzc2FnZQ==",
"retried": 3,
"maxRetries": 3,
"dlqId": "1725323658779-0",
"sourceMessageId": "msg_xxx",
"topicName": "myTopic",
"endpointName": "myEndpoint",
"url": "https://api.example.com/webhook",
"method": "POST",
"sourceHeader": { "content-type": "application/json" },
"sourceBody": "YmFzZTY0IGVuY29kZWQgcm9keQ==",
"notBefore": 1701198458025,
"createdAt": 1701198447054,
"scheduleId": "scd_xxx",
"callerIP": "178.247.74.179"
}Field Descriptions
status- HTTP status code from destinationheader- Response headers from destinationbody- Base64-encoded response body (may be truncated per plan limits)retried- Number of retry attempts mademaxRetries- Maximum retry limitdlqId- Dead Letter Queue ID (failure callbacks only)sourceMessageId- Original message IDtopicName- URL group name (if applicable)endpointName- Endpoint name within URL group (if applicable)url- Destination URLmethod- HTTP method usedsourceHeader- Original message headerssourceBody- Base64-encoded original message bodynotBefore- Scheduled delivery time (Unix ms)createdAt- Message creation time (Unix ms)scheduleId- Schedule ID (if from schedule)callerIP- IP address that published the message
Callback Configuration
Callbacks are themselves QStash messages and can be configured with the same options. Use the Upstash-Callback-* or Upstash-Failure-Callback-* header prefix:
Not available via SDK parameters - requires custom headers:
await client.publish({
url: "https://api.example.com/webhook",
body: "data",
callback: "https://api.example.com/callback",
headers: {
// Configure callback behavior
"Upstash-Callback-Retries": "3",
"Upstash-Callback-Timeout": "30",
"Upstash-Callback-Method": "PUT",
"Upstash-Callback-Delay": "60",
// Forward custom headers to callback
"Upstash-Callback-Forward-Authorization": "Bearer token",
"Upstash-Callback-Forward-X-Custom": "value",
// Configure failure callback
"Upstash-Failure-Callback-Retries": "5",
"Upstash-Failure-Callback-Forward-Authorization": "Bearer token",
},
});Available configuration headers:
Upstash-Callback-Retries/Upstash-Failure-Callback-RetriesUpstash-Callback-Timeout/Upstash-Failure-Callback-TimeoutUpstash-Callback-Delay/Upstash-Failure-Callback-DelayUpstash-Callback-Method/Upstash-Failure-Callback-MethodUpstash-Callback-Forward-*/Upstash-Failure-Callback-Forward-*
Notes
- Callbacks are charged as regular messages
- Callbacks retry until the callback URL returns 2XX or retries are exhausted
- Response body may be truncated if it exceeds your plan's message size limit
- Both URLs must be publicly accessible