Developer Console

Send a Message

Before you can use Amazon Device Messaging (ADM) to send a message to an instance of your app, your server must have:

  • Obtained and stored the app instance's registration ID. See Integrate Your App for information on the app side of this process.

  • Exchanged your client credentials for a current access token. See Request an Access Token for details.

After you meet the prerequisites, you can send messages with ADM in the Developer Console or programmatically.

Send a test message in the Developer Console

You can set up test messages for ADM in the Developer Console.

  1. Log in to the Developer Console.
  2. Hover over the Apps & Services tab and select Device messaging.
  3. In the Test your message screen, select the Target app.
  4. Enter the Client ID and secret and Device registration ID.
  5. Choose the duration of the message in the Message expires after section.
  6. Choose the message type.
  7. Select Send test message to complete your notification.

Check for the message on your device.

Message payloads and uniqueness

Using ADM, you can send messages with a payload of up to 6KB in size. For example, an instant-message program might send a message with a payload like the following:

"data":
   {
   "from":"Sam",
   "message":"Hey, Max. How are you?",
   "time":"10/26/2012 09:10:00"
   }

Alternately, some messages might not include any payload data at all. An example of this is a simple "sync"-type status message, where the message is only intended to notify the app or the user of a sync condition, not to transfer a payload of unique data. In this case, you can pass in an empty data parameter and include a value for the consolidationKey parameter. The consolidation key allows ADM to identify sync messages with redundant payloads and attempt to deliver only one of the messages to your app.

"data":{},
"consolidationKey":"Sync"

Sometimes, even payload-carrying messages are not intended to be unique. In this case, too, you would use the consolidationKey parameter to allow ADM to consolidate messages, when possible.

"data":
   {
   "message":"Download \"Catcher in the Rye\"",
   "url":"https:\/\/s3.amazon.com\/CatcherInTheRye"
   },
"consolidationKey":"NewDownload"

Request format

To send a message, your server component (from now on referred to as "your server") issues an HTTP POST request that looks similar to this:

POST /messaging/registrations/(REGISTRATION_ID_FOR_DESTINATION_APP_INSTANCE)/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"},
    "consolidationKey":"Some Key",
    "expiresAfter":86400
}

The first two lines of the request together comprise the URL for the POST request. The URL must include the registration ID for the specific instance of your app that should receive the message. You should have previously obtained this registration ID from the app instance. For an example of how your app might send your server its registration ID, see the ADM sample app included in the ADM SDK download package.

https://api.amazon.com/messaging/registrations/(REGISTRATION_ID_FOR_DESTINATION_APP_INSTANCE)/messages

The request itself consists of two parts: a header and a message body. 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 Atc|<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

For the content of the message body, you provide a JSONObject constructed of 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, 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 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":{} "data":{ "from":"Sam", "message":"Hey, Max. How are you?", "time":"10/26/2012 09:10:00" }
priority Optional value. There are two values for the priority of messages being sent: 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. Note that there are no guarantees that the previously enqueued messages will not be delivered. Your consolidation key can be no 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 60 (1 minute) to 2678400 (31 days), inclusive. The default value is 604800 (1 week).

For ADM on FireOS devices, the maximum supported value is 31 days. For ADM on Windows devices the maximum supported value is 7 days.
"expiresAfter":86400
md5 Optional value. This is a base-64-encoded MD5 checksum of the data parameter. If you provide a value for the md5 parameter, ADM verifies its accuracy. If you do not provide a value, the server calculates the value on your behalf. In either case, the server passes the md5 parameter's value through to the device and returns the calculated value within the x-amzn-data-md5 response header. See an example of calculating an MD5 checksum in the ADM Sample Code folder.

To ensure your md5 value matches that from ADM servers, you should calculate a value for this parameter as follows:

  1. Sort the message's key-value pairs using a UTF-8 code unit-based comparison of the keys.
  2. Concatenate the series of pairs in the format: "key1:value1,key2:value2".
  3. Remove any white spaces between the key/value pairs. There should be no whitespace between the ':' character and either the keys or values. There should also be no whitespace in between each pair and the ',' character.
  4. Compute the SHA-256 value using the UTF-8 bytes of the resulting string, according to the algorithm defined in RFC 1321.
  5. Base-64 encode the 128-bit output of the computation.

Response format

After successfully receiving and interpreting your POST request message, ADM servers send an HTTP response message that looks similar to this:

HTTP/1.1 200
X-Amzn-Data-md5: <md5 checksum>
X-Amzn-RequestId: <hexidecimal requiest ID>
Content-Type: application/json
X-Amzn-Type-Version: com.amazon.device.messaging.ADMSendResult@1.0
Content-Length: 308

{"registrationID":"amzn1.adm-registration.v1.<hexidecimal client credentials>"}

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:

  • registrationID: The current registration ID of the app instance. If this value is different from the one passed in by your server, your server must update its records to use this value.

ADM returns an error (non-200) status code if the message was not successfully accepted. In the case of a non-200 code, the response message might contain the following parameter in the body of the JSONObject:

  • reason: The reason the request was not accepted.

The following table describes possible ADM error status codes.

Code Description Example
400 Some argument of the input was invalid. Possible status codes include: InvalidRegistrationId, InvalidData, InvalidConsolidationKey, InvalidExpiration, InvalidChecksum, InvalidType, and Unregistered. Unregistered indicates that the app instance associated with the registration ID is no longer available to receive messages. You can receive an Unregistered status code if the owner of the device on which the app instance is registered has changed, or if the app instance has requested to no longer receive messages. InvalidRegistrationId indicates that the registration ID does not correspond to the sender identified by the provided access token. "reason": "InvalidRegistrationId"
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 rate of messages. The sender might retry later honoring the Retry-After header included in the response. To ensure high availability, ADM limits the number of messages that can be sent over a given period of time. If you have specific capacity requirements, please contact us and provide the following information with your request: 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 must retry later honoring the Retry-After header included in the response. n/a
503 The server is temporarily unavailable. The requester must retry later honoring the Retry-After header included in the response. n/a

The response header contains the following fields:

Field Description Example
X-Amzn-Data-md5 The calculated base-64-encoded MD5 checksum of the data field. X-Amzn-Data-md5: <md5 checksum>
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: <hexidecimal request ID>
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 an integer number of seconds (in decimal) after the time of the response or an HTTP-format date. See the HTTP/1.1 specification, section 14.37, for possible formats for this value. Retry-After: 120
Content-Type The content type of the resource: application/json Content-Type: application/json
X-Amzn-Type-Version Describes the format of the response. X-Amzn-Type-Version: com.amazon.device.messaging.ADMSendResult@1.0

Making the message request and handling the response

The following code sample is an example of how your server software might make a request to send a message and then handle the ADM servers' response:

/**
 * Request that ADM deliver your message to a specific instance of your app.
 */
public void sendMessageToDevice(String registrationId, String accessToken) throws Exception
{
    // JSON payload representation of the message.
    JSONObject payload = new JSONObject();

    // Define the key/value pairs for your 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);

    // 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");


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

    // Establish the base URL, including the section to be replaced by the registration
    // ID for the desired app instance. Because we are using String.format to create
    // the URL, the %1$s characters specify the section to be replaced.
    String admUrlTemplate = "https://api.amazon.com/messaging/registrations/%1$s/messages";

    URL admUrl = new URL(String.format(admUrlTemplate,registrationId));

    // 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 enqueue request failed with a " +
                                         "%d response code, with the following message: %s",
                                         responseCode, errorContent));
    }
    else
    {
        // The request was successful. The response contains the canonical Registration ID for the specific instance of your
        // app, which might be different that the one used for the request.

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

        String canonicalRegistrationId = parsedObject.getString("registrationID");

        // Check if the two Registration IDs are different.
        if(!canonicalRegistrationId.equals(registrationId))
        {
            // At this point the data structure that stores the Registration ID values should be updated
            // with the correct Registration ID for this particular app instance.
        }
    }

}

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: Mar 27, 2023