All serverless function providers have a maximum execution time for each function. Usually you can extend this time by paying more, but it’s still limited. QStash provides a way to go around this problem by using callbacks.

What is a callback?

A callback allows you to call a long running function without having to wait for its response. Instead of waiting for the request to finish, you can add a callback url to your published message and when the request finishes, we will call your callback URL with the response.

  1. You publish a message to QStash using the /v2/publish endpoint
  2. QStash will enqueue the message and deliver it to the destination
  3. QStash waits for the response from the destination
  4. When the response is ready, QStash calls your callback URL with the response

Callbacks publish a new message with the response to the callback URL. Messages created by callbacks are charged as any other message.

How do I use Callbacks?

You can add a callback url in the Upstash-Callback header when publishing a message. The value must be a valid URL.

curl -X POST \
  https://qstash.upstash.com/v2/publish/https://my-api... \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <QSTASH_TOKEN>' \
  -H 'Upstash-Callback: <CALLBACK_URL>' \
  -d '{ "hello": "world" }'

The callback body sent to you will be a JSON object with the following fields:

{
  "status": 200,
  "header": { "key": "value"},            // Response header 
  "body": "YmFzZTY0IGVuY29kZWQgcm9keQ==", // base64 encoded response body
  "retried": 2,                           // How many times we retried to deliver the original message
  "maxRetries" : 3,                       // Number of retries before the message assumed to be failed to delivered.
  "sourceMessasgeId": "msg_xxx",          // The ID of the message that triggered the callback
  "topicName" : "myTopic",                // The name of the topic if the request was part of a topic 
  "endpointName" : "myEndpoint",          // The endpoint name if the endpoint is given a name within a topic
  "url" : "http://myurl.com" ,            // The destination url of the message that triggered the callback
  "method" : "GET",                       // The http method of the message that triggered the callback
  "sourceHeader" : { "key": "value"},     // The http header of the message that triggered the callback
  "sourceBody" : "YmFzZTY0kZWQgcm9keQ==", // The base64 encoded body of the message that triggered the callback
  "notBefore" : "1701198458025",          // The unix timestamp of the message that triggered the callback is/will be delivered in milliseconds
  "createdAt" : "1701198447054",          // The unix timestamp of the message that triggered the callback is created in milliseconds
  "scheduleId" : "scd_xxx"                // The scheduleId of the message if the message is triggered by a schedule
  "callerIP": "178.247.74.179"            // The IP address where the message that triggered the callback is published from
}

In Next.js you could use the following code to handle the callback:

// pages/api/callback.js

import { verifySignature } from "@upstash/qstash/dist/nextjs";

function handler(req, res) {
  // responses from qstash are base64-encoded
  const decoded = atob(req.body.body);
  console.log(decoded);

  return res.status(200).end();
}

export default verifySignature(handler);

export const config = {
  api: {
    bodyParser: false,
  },
};

We may truncate the response body if it exceeds your plan limits. You can check your Max Message Size in the console.

Make sure you verify the authenticity of the callback request made to your API by verifying the signature.

What is a Failure-Callback?

Failure Callbacks are only available in v2

Failure callbacks are similar to callbacks but they are called only when all the retries are exhausted and still the message can not be delivered to the given endpoint.

This is designed to be an alternative to List messages to DLQ. See Migration Guide for more details for the detailed flow how to use them.

You can add a failure callback URL in the Upstash-Failure-Callback header when publishing a message. The value must be a valid URL.

curl -X POST \
  https://qstash.upstash.com/v2/publish/<DESTINATION_URL> \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <QSTASH_TOKEN>' \
  -H 'Upstash-Failure-Callback: <CALLBACK_URL>' \
  -d '{ "hello": "world" }'

The callback body sent to you will be a JSON object with the following fields:

{
  "status": 200,
  "header": { "key": "value"},            // Response header 
  "body": "YmFzZTY0IGVuY29kZWQgcm9keQ==", // base64 encoded response body
  "retried": 3,                           // How many times we retried to deliver the original message
  "maxRetries" : 3,                       // Number of retries before the message assumed to be failed to delivered.
  "dlqId" : dlqId                         // Dead Letter Queue id. This can be used to retrieve/remove the related message from DLQ.
  "sourceMessasgeId": "msg_xxx",          // The ID of the message that triggered the callback
  "topicName" : "myTopic",                // The name of the topic if the request was part of a topic 
  "endpointName" : "myEndpoint",          // The endpoint name if the endpoint is given a name within a topic
  "url" : "http://myurl.com" ,            // The destination url of the message that triggered the callback
  "method" : "GET",                       // The http method of the message that triggered the callback
  "sourceHeader" : { "key": "value"},     // The http header of the message that triggered the callback
  "sourceBody" : "YmFzZTY0kZWQgcm9keQ==", // The base64 encoded body of the message that triggered the callback
  "notBefore" : "1701198458025",          // The unix timestamp of the message that triggered the callback is/will be delivered in milliseconds
  "createdAt" : "1701198447054",          // The unix timestamp of the message that triggered the callback is created in milliseconds
  "scheduleId" : "scd_xxx",               // The scheduleId of the message if the message is triggered by a schedule
  "callerIP": "178.247.74.179"            // The IP address where the message that triggered the callback is published from
}

You can also use a callback and failureCallback together!