Developer Console

Topic Based Messaging

Topic Based Messaging (TBM) allows you to send messages to groups of users subscribed to the same topic. This is done through multicast communication. When an app is installed on a device, the instance of that app has a unique identifier called a device registration ID. Topic Based Messages can be delivered to an individual device using this ID, allowing you to send messages to more than one app instance in a single API call. Rather than send a message to an individual device registration ID, you send a message to a topic. Users are subscribed to that topic and Amazon Device Messaging (ADM) routes the messages to all device registration IDs associated with that topic. Types of messages 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 is released.

Considerations for Topic Based Messaging

Here are some important considerations about Topic Based Messages:

  • Topic Based Messages target a specific security profile, meaning topics can send messages to app instances of multiple apps registered with the same security profile.
  • Amazon Device Messaging (ADM) enforces these limits in working with topics:
    • One security profile can be allocated with a maximum of 100 topics.
    • One app instance can be subscribed to a maximum of 100 topics.
    • One topic can be subscribed to a maximum of 10000 app instances.
  • Topic Based Messages strive 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, stock quotes.

Register a security profile for Topic Based Messaging

Topic Based Messaging allows you to 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. See Integrate Your App for information on the app side of this process.
  • Exchange your client credentials for a current access token. See Request an Access Token for details.

After you meet these prerequisites, you can register the security profile associated with the generated accessToken for TopicBasedMessaging 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 like this:

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"
}

The POST URL is made up of the second line (Host) and the first line (POST). When you add those together, you get the full URL, like this:

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 these 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 JSONObject 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 this:

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 a 200 status code if the security profile for the client credentials is registered with TopicBasedMessaging. If you receive a 200 status code, the response message will contain the following parameter in a JSONObject:

  • message: Contains the security profile associated with the credentials you used to create the accessToken, and the clientSecret used in the message body. The response should be that the security profile is now registered with TopicBasedMessaging.

ADM returns an error code if the security profile for the client credentials isn’t registered with TopicBasedMessaging. For a code other than 200, the response message might contain this parameter in the JSONObject body:

  • reason: The reason the request was not accepted

See below for more error status codes.

ADM error status codes

Code Description Example
400 The clientSecret in the message body isn't the same as the one used to create the accessToken. The sender should use the same clientSecret in the message body as the one used to create the accessToken. "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 may 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 may retry later according to the Retry-After header included in the response. n/a
503 The server is temporarily unavailable. The requester may 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 uniquely identifies the request. In the unlikely event that you have problems with ADM, Amazon can use this value to troubleshoot the problem. X-Amzn-RequestId: <Amazon RequestId>
Retry-After This field is returned in the case of a 429, 500, or 503 error response. Retry-After 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. See the HTTP/1.1 specification, section 10.2.3, for possible formats for this value. 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 TopicBasedMessaging, 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 the client app instance to a topic, or unsubscribe it from a topic

Client apps can subscribe to any existing topic or create a new topic. When a client app subscribes to a new topic not currently existing in the associated security profile, a new topic is created, and any app associated with that security profile can subscribe to it.

To subscribe to a topic, the app calls subscribeToTopic() on ADM with the topic name. Your app is notified via onSubscribe() or onSubscribeError(). 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. Your app will be notified via onUnsubscribe() or onUnsubscribeError(). 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 new APIs might not be available on older Fire OS devices, which may cause apps on those devices to crash. To avoid this, use a try/catch block when calling these APIs as demonstrated below.

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 below,
(i) your security profile is allocated with 100 topics
(ii) your app instance is subscribed to 100 topics
(iii) the topic is subscribed with 10000 app instances/registrationIds.
TOO_MANY_REQUESTS Too many subscribe/unsubscribe request. Retry after 30 seconds.
SERVICE_NOT_AVAILABLE Unable to communicate with the ADM service. A very rare to occurrance.

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 like this:

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"
}

The POST URL is made up of the second line (Host) and the first line (POST). When you add those together, you get the full URL, like this:

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 JSONObject with a string containing the following parameters.

Parameter Description Example
data The payload data to send with the message. The data must be in the form of JSON-formatted key/value pairs; both keys and values must be String values. The total size of the data cannot be greater than 6KB (if not provided with the notification field in the message body), including both key(s) and value(s), as well as 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 are not included in the calculation of the payload size. If the message does not include payload data, as in the case of 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 cannot be greater than 6KB.
"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 pre-defined 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 cannot be greater than 6KB.
"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. Normal priority messages can be delivered immediately if the app is in the foreground. When the device is in doze mode, delivery may 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. This is an arbitrary string used to indicate that multiple messages are logically the same, and that ADM is allowed to drop previously enqueued messages in favor of this new one. There are no guarantees that the previously enqueued messages will not be delivered. Your consolidation key cannot 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, the message might be discarded. 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 this:

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

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

ADM returns a 200 status code if the message was accepted and is enqueued for delivery to the device. In the case of a 200 code, the response message contains the following parameter in a JSONObject:

  • 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 the message was not successfully accepted. In this case, the response message may contain the following parameter in the body of the JSONObject:

  • reason: The reason the request was not accepted.

ADM error status codes

The following table describes possible ADM error status codes.

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 paramter, 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 accessToken is not registered with TopicBasedMessaging.
"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 may 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 may retry later according to the Retry-After header directions included in the response. n/a
503 The server is temporarily unavailable. The requester may 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. In the unlikely event that you have problems with ADM, Amazon can use this value to troubleshoot the problem. X-Amzn-RequestId: <Amazon RequestId>
Retry-After This field is returned in the case of a 429, 500, or 503 error response. 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. See the HTTP/1.1 specification, section 10.2.3, for possible formats for this value. 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: Jan 20, 2023