as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
Ring
AWS
Documentation
Support
Contact Us
My Cases
Develop
Test
Publish
Monetize
Engage users
Device specifications
Resources

Topic-Based Messaging

Topic-Based Messaging (TBM) uses multicast communication to send messages to groups of users who subscribe to the same topic. When a user installs an app on a device, the app instance receives a unique identifier called a device registration ID. In ADM, you can use this ID to deliver a message to an individual device. However, with Topic-Based Messaging, you can send messages to multiple app instances in a single API call. Instead of sending a message to an individual device registration ID, you send a message to a topic. Users subscribe to that topic, and Amazon Device Messaging (ADM) routes the messages to all device registration IDs associated with that topic. Message types include data messages, notification messages, and notification messages with data.

You can use topics to engage with particular segments of your customers. For example, if you developed a weather forecasting app, users could opt in to a topic for their geographical location and receive notifications for changes in the weather in their area. For a video streaming app, users could subscribe to their genres of interest to get automatic updates whenever a new movie or series of that genre releases.

Considerations for Topic-Based Messaging

Here are some important considerations about Topic-Based Messaging:

  • Topic-Based Messaging targets a specific security profile. This means that when multiple apps share the same security profile, Topic-Based Messaging can reach all app instances associated with that security profile.
  • Amazon Device Messaging (ADM) enforces these limits in working with topics:
    • One security profile can have a maximum of 100 topics allocated to it.
    • One app instance can subscribe to a maximum of 100 topics.
    • One topic can have a maximum of 10,000 app instances subscribed to it.
  • Topic-Based Messaging strives for higher throughput rather than lower latency. To send messages to individual devices, target messages to device registration IDs instead of topics.
  • Topic-Based Messaging is best for publicly available resources, such as weather forecasts, and stock quotes.

Register a security profile for Topic-Based Messaging

With Topic-Based Messaging, you can do the following:

  • Subscribe a device registration ID to a topic
  • Unsubscribe a device registration ID from a topic
  • Send a message to a topic

Prerequisites

To use Topic-Based Messaging, complete these prerequisites.

  • Obtain and store the app instance's registration IDs. For information about the app-side of this process, see Integrate Your App.
  • Exchange your client credentials for a current access token. For details, see Request an Access Token.

After you meet these prerequisites, you can register the security profile associated with the generated access token for Topic-Based Messaging programmatically.

Request format

To register your security profile with Topic-Based Messaging, your server component (from now on referred to as "your server") must issue an HTTP POST request as shown in the following example.

POST /v1/messaging/topic/registrations HTTP/1.1
Host: api.amazon.com
Authorization: Bearer (YOUR_ACCESS_TOKEN)
Content-Type: application/json
Accept: application/json

{
    "clientSecret":"YOUR_CLIENT_SECRET"
}

To get the complete POST URL, combine the second line (Host) and the first line (POST) as shown here.

https://api.amazon.com/v1/messaging/topic/registrations

Request requirements

The request itself consists of two parts: the header and message body.

Header fields

The header must contain the following fields.

Field Description Example
Authorization Include your current access token here. Value must be: Bearer (YOUR_ACCESS_TOKEN) Authorization: Bearer <Your access token>
Content-Type Value must be: application/json Content-Type: application/json
Accept Value must be: application/json Accept: application/json

Message body parameter

For the message body content, provide a JSON object with a string containing the following parameters.

Parameter Description Example
clientSecret The "client secret" portion of your client credentials. clientSecret=<YOUR_CLIENT_SECRET>

Response format

After successfully receiving and interpreting your POST request message, ADM servers send an HTTP response message similar to the following.

HTTP/1.1 200
X-Amzn-RequestId: <Amazon RequestId>
Content-Type:application/json
content-length:140

{
    "message": "The security profile amzn1.application.<32 hexadecimal characters> is registered with TopicBasedMessaging."
}

ADM returns an HTTP 200 status code if the security profile for the client credentials is registered with Topic-Based Messaging. If you receive a 200 status code, the response message contains the following parameter in a JSON object:

  • message: Contains the security profile associated with the credentials you used to create the access token, and the clientSecret parameter used in the message body. The response should be that the security profile is now registered with Topic-Based Messaging.

ADM returns an error code if the security profile for the client credentials isn't registered with Topic-Based Messaging. For a code other than 200, the response message might contain this parameter in the JSON object body:

  • reason: The reason the request was not accepted

For a description of the HTTP error status codes, see the following section.

ADM HTTP error status codes

HTTP status code Description Example
400 The clientSecret in the message body isn't the same as the one used to create the access token. The sender should use the same clientSecret in the message body as the one used to create the access token. "reason": "The given clientSecret is not associated with the security profile amzn1.application.<32 hexadecimal characters>"
401 The access token provided was invalid. The sender should refresh their access token. For information about refreshing an access token, see Request an Access Token. "reason":"AccessTokenExpired"
429 The requester has exceeded their maximum allowable message rate. The sender can retry later according to the Retry-After header directions included in the response. To ensure high availability, ADM limits the number of messages sent over a given period of time. If you have specific capacity requirements, contact us and provide the following information:
- your name
- company name
- your email address
- requested TPS (transactions per second) limit
- reason
"reason":"MaxRateExceeded"
500 There was an internal server error. The requester can retry later according to the Retry-After header included in the response. n/a
503 The server is temporarily unavailable. The requester can try later according to the Retry-After header directions included in the response. n/a

Response header fields

Field Description Example
X-Amzn-RequestId A value created by ADM that uniquely identifies the request. If you have problems with ADM, Amazon can use this value to troubleshoot the problem. X-Amzn-RequestId: <Amazon RequestId>
Retry-After Error responses with HTTP status codes of 429, 500, or 503 include this field. The Retry-After message specifies how long the service is expected to be unavailable. This value can be either a decimal number of seconds after the time of the response, or an HTTP-format date. For possible formats for this value, see the HTTP/1.1 specification, section 10.2.3. Retry-After: 30
Content-Type The resource content type: application/json Content-Type: application/json

Make a TBM registration request

Here is an example of how your server software might make a request to register a security profile with Topic-Based Messaging, and handle the ADM server response.

/**
 * Request that ADM registers your security profile with TopicBasedMessaging.
 */
public void tbmRegistration(String clientSecret, String accessToken) throws Exception
{
    // JSON payload representation of the message.
    JSONObject payload = new JSONObject();

    // Add clientSecret value which is used to create the accessToken provided in the header.
    payload.put("clientSecret", clientSecret);


    // Convert the message from a JSON object to a string.
    String payloadString = payload.toString();

    // Establish the base URL.
    String admUrlTemplate = "https://api.amazon.com/v1/messaging/topic/registrations";

    URL admUrl = new URL(admUrlTemplate);

    // Generate the HTTPS connection for the POST request. You cannot make a connection
    // over HTTP.
    HttpsURLConnection conn = (HttpsURLConnection) admUrl.openConnection();
    conn.setRequestMethod("POST");
    conn.setDoOutput(true);

    // Set the content type and accept headers.
    conn.setRequestProperty("content-type", "application/json");
    conn.setRequestProperty("accept", "application/json");
    
    // Add the authorization token as a header.
    conn.setRequestProperty("Authorization", "Bearer " + accessToken);

    // Obtain the output stream for the connection and write the message payload to it.
    OutputStream os = conn.getOutputStream();
    os.write(payloadString.getBytes(), 0, payloadString.getBytes().length);
    os.flush();
    conn.connect();

    // Obtain the response code from the connection.
    int responseCode = conn.getResponseCode();

    // Check if we received a failure response, and if so, get the reason for the failure.
    if( responseCode != 200)
    {
        if( responseCode == 401 )
        {
            // If a 401 response code was received, the access token has expired. The token should be refreshed
            // and this request can be retried.
        }

        String errorContent = parseResponse(conn.getErrorStream());
        throw new RuntimeException(String.format("ERROR: The request to register the security profile with TBM has failed with a " +
                                         "%d response code, with the following message: %s",
                                         responseCode, errorContent));
    }
    else
    {
        // The request was successful. The response contains the security profile which is registered to TopicBasedMessaging.
        String responseContent = parseResponse(conn.getInputStream());
        JSONObject parsedObject = new JSONObject(responseContent);

        String message = parsedObject.getString("message");
        
        // You can extract the security profile from the message to verify if it's the same as the one used to
        // create the accessToken used in the request header.
        
    }

}

private String parseResponse(InputStream in) throws Exception
{
    // Read from the input stream and convert into a String.
    InputStreamReader inputStream = new InputStreamReader(in);
    BufferedReader buff = new BufferedReader(inputStream);

    StringBuilder sb = new StringBuilder();
    String line = buff.readLine();
    while(line != null)
   {
      sb.append(line);
      line = buff.readLine();
    }

    return sb.toString();
}

Subscribe or unsubscribe the client app instance to a topic

Client apps can subscribe to any existing topic or create a new topic. When a client app subscribes to a new topic that doesn't exist in the associated security profile, a new topic is created. Any app associated with that security profile can subscribe to the newly created topic.

To subscribe to a topic, the app calls subscribeToTopic() on ADM with the topic name. ADM notifies your app through the onSubscribe() or onSubscribeError() callback. Your app should override these callback methods defined in the com.amazon.device.messaging.ADMMessageHandlerJobBase and com.amazon.device.messaging.ADMMessageHandlerBase classes.

Subscribe example

final ADM adm = new ADM(this);
// subscribeToTopic() is asynchronous; your app will be notified via
// onSubscribe() callback if subscribeToTopic() succeeds.
// onSubscribeError() callback if subscribeToTopic() fails.
adm.subscribeToTopic("weather");

To unsubscribe from a topic, the app calls ADM unsubscribeFromTopic() with the topic name. ADM notifies your app through the onUnsubscribe() or onUnsubscribeError() callback. Your app should override these callback methods defined in the com.amazon.device.messaging.ADMMessageHandlerJobBase and com.amazon.device.messaging.ADMMessageHandlerBase classes.

Unsubscribe example

final ADM adm = new ADM(this);
// unsubscribeFromTopic() is asynchronous; your app will be notified via
// onUnsubscribe() callback if unsubscribeFromTopic() succeeds.
// onUnsubscribeError() callback if unsubscribeFromTopic() fails.
adm.unsubscribeFromTopic("weather");

These APIs might not be available on older Fire OS devices, which could cause apps on those devices to crash. To avoid this, use a try-catch block when calling these APIs as shown in the following example.

try {
    Log.d(TAG, "subscribeToTopic: Trying to subscribe to topic: " + topic);
    adm.subscribeToTopic(topic);
} catch (Error e) {
    Log.d(TAG, "subscribeToTopic: Error Caught and Error Message is " + e.getMessage());
}

Callback method description

Callback method Description
onSubscribe() Called on successful subscription to a topic with parameters topic (topic the app is subscribed to) and application context.
onSubscribeError() Called when a subscription request fails with parameter topic (topic the app was trying to subscribe to), error ID, and application context.
onUnsubscribe() Called on successful unsubscription from a topic with parameter topic (topic the app is unsubscribed from), and application context.
onUnsubscribeError() Called when an unsubscription request fails with parameter topic (topic the app was trying to unsubscribe from), error ID, and application context.

Error ID and description

Error ID Description
INVALID_MANIFEST Manifest doesn't have required permissions/broadcast receiver intent-filters defined. Check the integration steps and update the manifest file.
INTERNAL_ERROR Internal unhandled error similar to an HTTP 500 status code. No need to do anything.
INVALID_TOPIC Provided topic is not valid. It should not be null and topic naming should follow this pattern [a-zA-Z0-9-_.~%]{1,100}.
NOT_REGISTERED_WITH_TBM App is not registered for TBM. Register your app for Topic-Based Messaging.
UNREGISTERED App is not registered or registration ID is Invalid. Call ADM startRegister() to register again.
ALREADY_SUBSCRIBED App is already subscribed to the topic. Do not try to subscribe again.
NOT_SUBSCRIBED App is already unsubscribed, or the given topic does not exist. Do not try to unsubscribe from this topic.
MAXIMUM_SUBSCRIPTION_EXCEEDED Maximum subscription limit exceeded means either of the following,
(i) your security profile is allocated with 100 topics
(ii) your app instance is subscribed to 100 topics
(iii) the topic is subscribed with 10,000 app instances (registration IDs).
TOO_MANY_REQUESTS Too many subscribe/unsubscribe requests. Retry after 30 seconds.
SERVICE_NOT_AVAILABLE Unable to communicate with the ADM service.

Sample code for callback methods

public class MyADMMessageHandler extends ADMMessageHandlerJobBase{
    @Override
    protected void onSubscribe(final Context context, final String topic) {
        // Write your logic here for successful subcription like
        // notifying customer
        // sending the details to your server
    }

    @Override
    protected void onSubscribeError(final Context context, final String topic, final String errorId) {
        // Write your logic here for subscription error based on error Id
    }

    @Override
    protected void onUnsubscribe(final Context context, final String topic) {
        // Write your logic here for successful unsubscription like
        // notifying customer
        // sending the details to your server
    }

    @Override
    protected void onUnsubscribeError(final Context context, final String topic, final String errorId) {
        // Write your logic here for unsubscription error based on error Id
    }

}

Receive and handle topic messages

ADM delivers messages to a topic in the same way it delivers to a single registration ID. See ADM Message Types for more information.

Send a message to topic

Prerequisites

To send a message to a topic, complete these prerequisites.

After you meet these prerequisites, you can send a message to the topic with ADM programmatically.

Request format

To send a message to a topic, your server issues an HTTP POST request, as shown in the following example.

POST /v1/messaging/topic/messages HTTP/1.1
Host: api.amazon.com
Authorization: Bearer (YOUR_ACCESS_TOKEN)
Content-Type: application/json
X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage@1.0
Accept: application/json
X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult@1.0

{
    "data":{"key1":"value1","key2":"value2"},
    "notification":{"title":"ADM Message Notification","body":"We have a new offer for you!"},
    "consolidationKey":"Some Key",
    "expiresAfter":86400,
    "priority": "high",
    "topic":"SomeTopic"
}

To get the complete POST URL, combine the second line (Host) and the first line (POST) as shown here.

https://api.amazon.com/v1/messaging/topic/messages

Request requirements

The request itself consists of two parts: a header and a message body.

Header fields

The header must contain the following fields:

Field Description Example
Authorization Include your current access token here. Value must be: Bearer (YOUR_ACCESS_TOKEN) Authorization: <Your access token>
Content-Type Value must be: application/json Content-Type: application/json
X-Amzn-Type-Version Value must be: com.amazon.device.messaging.ADMMessage@1.0 X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage@1.0
Accept Value must be: application/json Accept: application/json
X-Amzn-Accept-Type Value must be: com.amazon.device.messaging.ADMSendResult@1.0 X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult@1.0

Message body parameters

For message body content, provide a JSON object with a string containing the following parameters.

Parameter Description Example
data The payload data to send with the message. The data must be in JSON-formatted key-value pairs; both keys and values must be String values. The total size of the data can't be greater than 6 KB (if not provided with the notification field in the message body). The total size includes the keys and values, the quotes that surround them, the ":" character that separates them, the commas that separate the pairs, and the opening and closing brace around the field. Whitespaces between key-value pairs aren't included in the calculation of the payload size. If the message doesn't include payload data, such as with a sync message, you can pass in an empty object, for example, "data":{}.

If both data and notification payload are provided in the sent message, the combined total size can't be greater than 6 KB.
"data":{ "from":"Sam", "message":"Hey, Max. How are you?", "time":"10/26/2012 09:10:00" }
notification The payload notification to send with the message. Notification messages require using predefined key-value pairs. Here are the key-value pairs supported by ADM notifications.

If the data and notification payload are provided in the message, the combined total size can't be greater than 6 KB.
"notification":{"title":"ADM Message Notification","body":"We have a new offer for you!"}
priority Optional value. There are two values for the sent message priority: normal and high priority. Normal is the default priority for messages. ADM can deliver normal priority messages immediately if the app is in the foreground. When the device is in doze mode, delivery might be delayed until the next maintenance window. For high priority messages, ADM attempts to deliver the message to the app immediately, even when in doze mode. The number of high priority messages an app can receive in a day is limited by the app's standby bucket. The default value is normal. "priority": "high"
consolidationKey Optional value. An arbitrary string used to indicate that multiple messages are logically the same, and that ADM can drop previously enqueued messages in favor of this new one. There are no guarantees that ADM won't deliver the previously enqueued messages. Your consolidation key can't be greater than 64 characters in length. "consolidationKey":"SyncNow"
expiresAfter Optional value. The number of seconds that ADM should retain the message if the device is offline. After this time, ADM can discard the message. Allowed values range from 1 (1 second) to 2678400 (31 days), inclusive. The default value is 604800 (1 week). "expiresAfter":86400
topic Mandatory value. The topic to which your app instances are subscribed, and to which the message is to be sent. A string value should satisfy the regex pattern [a-zA-Z0-9-_.~%]{1,100}. "topic":"SomeTopic"

Response format

After successfully receiving and interpreting your POST request message, ADM servers send an HTTP response message like the following.

HTTP/1.1 200
X-Amzn-RequestId: <Amazon RequestId>
Content-Type: application/json
Content-Length: 308

{
    "messageId":"<Message ID>"
}

ADM returns an HTTP 200 status code if it accepts the message and enqueues it for delivery to the device. For the 200 response code, the response message contains the following parameter in a JSON object:

  • messageId: An ID associated with the message sent, and the topic to which it is sent.

ADM returns an error (non-200) status code if it doesn't accept the message. In this case, the response message might contain the following parameter in the body of the JSON object:

  • reason: The reason the request was not accepted.

ADM HTTP error status codes

The following table describes possible HTTP error status codes when sending a topic-based message with the ADM service.

HTTP status code Description Example
400 In these scenarios, the request is rejected with a 400 response code.

1. The topic value does not satisfy the regex pattern [a-zA-Z0-9-_.~%]{1,100}.

2. The topic has no active subscriptions, meaning no registrationIds are subscribed to it.

3. An input parameter, besides the topic, is invalid such as:
- InvalidData - Neither the data nor notification field is provided.
- InvalidConsolidationKey - The length of the key is greater than 64.
- InvalidExpiration - The value is less than 1 or greater than 2678400.
- InvalidType - The typeVersion and acceptType values provided in the header are not allowed values.

4. The security profile identified by the access token is not registered with Topic-Based Messaging.
"reason": "Security profile amzn1.application.<32 hexadecimal characters> is not registered for TopicBasedMessaging."
401 The access token provided was invalid. The sender should refresh their access token. For information about refreshing an access token, see Request an Access Token. "reason":"AccessTokenExpired"
413 The message payload provided in the data parameter exceeded the maximum allowable data size (6 KB). "reason":"MessageTooLarge"
429 The requester has exceeded their maximum allowable message rate. The sender can retry later according to the Retry-After header directions included in the response. To ensure high availability, ADM limits the number of messages sent over a given period of time. If you have specific capacity requirements, contact us and provide the following information:
- your name
- company name
- your email address
- requested TPS (transactions per second) limit
- reason
"reason":"MaxRateExceeded"
500 There was an internal server error. The requester can retry later according to the Retry-After header directions included in the response. n/a
503 The server is temporarily unavailable. The requester can try later according to the Retry-After header directions included in the response. n/a

Response header fields

Field Description Example
X-Amzn-RequestId A value created by ADM that uniquely identifies the request. If you have problems with ADM, Amazon can use this value to troubleshoot the problem. X-Amzn-RequestId: <Amazon RequestId>
Retry-After Error responses with HTTP status codes of 429, 500, or 503 include this field. The Retry-After message specifies how long the service is expected to be unavailable. This value can be either a decimal number of seconds after the time of the response, or an HTTP-format date. For possible formats for this value, see the HTTP/1.1 specification, section 10.2.3. Retry-After: 30
Content-Type The resource content type: application/json Content-Type: application/json

Send a message to a topic and handle the response

The following code sample demonstrates how your server software might send a message to a topic, and handle the ADM server response.

/**
 * Request that ADM delivers your message to a specific instances of your app which are subscribed to the given topic.
 */
public void sendMessageToTopic(String topic, String accessToken) throws Exception
{
    // JSON payload representation of the message.
    JSONObject payload = new JSONObject();

    // Define the key/value pairs for your data message content and add them to the
    // message payload.
    JSONObject data = new JSONObject();
    data.put("firstKey", "firstValue");
    data.put("secondKey", "secondValue");
    payload.put("data", data);
    
    // Define the key/value pairs for your notification message content and add them to the
    // message payload.
    // Notification message only accepts a pre-defined key-value pairs.
    // The key-value pairs supported by ADM notifications can be found from the documentation
    // (https://developer.amazon.com/docs/adm/message-types.html#notification).
    JSONObject notification = new JSONObject();
    notification.put("title", "ADM Message Notification");
    notification.put("body", "We have a new offer for you!");
    payload.put("notification", notification);

    // Add a consolidation key. If multiple messages are pending delivery for a particular
    // app instance with the same consolidation key, ADM will attempt to delivery the most
    // recently added item.
    payload.put("consolidationKey", "ADM_Enqueue_Sample");

    // Add an expires-after value to the message of 1 day. If the targeted app instance does not
    // come online within the expires-after window, the message will not be delivered.
    payload.put("expiresAfter", 86400);

    // Add priority to the message. The value of priority determines whether the message will be
    // delivered when the device is in doze/idle mode.
    payload.put("priority", "high");
    
    // Add a topic to the message body, using which the message is to be delivered to the app instances.
    payload.put("topic", topic);


    // Convert the message from a JSON object to a string.
    String payloadString = payload.toString();

    // Establish the base URL
    String admUrlTemplate = "https://api.amazon.com/v1/messaging/topic/messages";

    URL admUrl = new URL(admUrlTemplate);

    // Generate the HTTPS connection for the POST request. You cannot make a connection
    // over HTTP.
    HttpsURLConnection conn = (HttpsURLConnection) admUrl.openConnection();
    conn.setRequestMethod("POST");
    conn.setDoOutput(true);

    // Set the content type and accept headers.
    conn.setRequestProperty("content-type", "application/json");
    conn.setRequestProperty("accept", "application/json");
    conn.setRequestProperty("X-Amzn-Type-Version", "com.amazon.device.messaging.ADMMessage@1.0");
    conn.setRequestProperty("X-Amzn-Accept-Type", "com.amazon.device.messaging.ADMSendResult@1.0");

    // Add the authorization token as a header.
    conn.setRequestProperty("Authorization", "Bearer " + accessToken);

    // Obtain the output stream for the connection and write the message payload to it.
    OutputStream os = conn.getOutputStream();
    os.write(payloadString.getBytes(), 0, payloadString.getBytes().length);
    os.flush();
    conn.connect();

    // Obtain the response code from the connection.
    int responseCode = conn.getResponseCode();

    // Check if we received a failure response, and if so, get the reason for the failure.
    if( responseCode != 200)
    {
        if( responseCode == 401 )
        {
            // If a 401 response code was received, the access token has expired. The token should be refreshed
            // and this request can be retried.
        }

        String errorContent = parseResponse(conn.getErrorStream());
        throw new RuntimeException(String.format("ERROR: The send message to the topic %s request failed with a " +
                                         "%d response code, with the following message: %s",
                                         topic, responseCode, errorContent));
    }
    else
    {
        // The request was successful. The response contains the messageId 
        // which is associated with the message sent and the topic to which it is sent to.

        String responseContent = parseResponse(conn.getInputStream());
        JSONObject parsedObject = new JSONObject(responseContent);

        String messageId = parsedObject.getString("messageId");
        // The messageId can be communicated with ADM to troubleshoot the issues in case the message is
        // not delivered to any of the app instances.
}

private String parseResponse(InputStream in) throws Exception
{
    // Read from the input stream and convert into a String.
    InputStreamReader inputStream = new InputStreamReader(in);
    BufferedReader buff = new BufferedReader(inputStream);

    StringBuilder sb = new StringBuilder();
    String line = buff.readLine();
    while(line != null)
   {
      sb.append(line);
      line = buff.readLine();
    }

    return sb.toString();
}

Last updated: Feb 13, 2026