<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: miruky</title>
    <description>The latest articles on DEV Community by miruky (@miruky).</description>
    <link>https://dev.to/miruky</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3944262%2F8032babd-171e-4c07-abf6-5ba2bdea6e53.png</url>
      <title>DEV Community: miruky</title>
      <link>https://dev.to/miruky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/miruky"/>
    <language>en</language>
    <item>
      <title>Transform an EventBridge Event Before It Reaches Amazon SQS</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Sat, 05 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/transform-an-eventbridge-event-before-it-reaches-amazon-sqs-9de</link>
      <guid>https://dev.to/miruky/transform-an-eventbridge-event-before-it-reaches-amazon-sqs-9de</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;An EventBridge rule normally forwards its matching event to a target. An input transformer changes that behavior: JSONPath variables select values from the original event, and an input template builds the payload that the target receives instead.&lt;/p&gt;

&lt;p&gt;This Console run first sends a deliberately nonmatching control event, then sends one matching synthetic order event to a Standard SQS queue. The matching source event contains &lt;code&gt;orderId&lt;/code&gt;, &lt;code&gt;total&lt;/code&gt;, and an unwanted &lt;code&gt;customerNote&lt;/code&gt;; the queue receives only the order ID, total, EventBridge timestamp, and a fixed source label.&lt;/p&gt;

&lt;p&gt;The exercise uses two custom events and a small number of SQS requests. Check the linked EventBridge and SQS pricing pages for current rates and free-tier terms before running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create an event bus and a queue
&lt;/h2&gt;

&lt;p&gt;The run uses the English AWS Console and keeps EventBridge, SQS, and every validation action in &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi24n4chymj1kyu1y0ni2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi24n4chymj1kyu1y0ni2.png" alt="The English AWS Console shows United States (N. Virginia) before the EventBridge resources are created." width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header confirms &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the EventBridge Console is in English. This fixes the Region shared by the bus, rule, and queue.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;Amazon EventBridge&lt;/strong&gt;, choose &lt;strong&gt;Event buses&lt;/strong&gt;, and search for &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt;. An exact-name result should not exist before creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp6iy9ynoh3u3724zzl6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp6iy9ynoh3u3724zzl6.png" alt="The EventBridge event-bus list has no exact match for the generated name." width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact filter for &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt; returns no event bus. That empty result establishes the resource boundary before creation.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create event bus&lt;/strong&gt; and enter &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt;. Keep &lt;strong&gt;Use AWS owned key&lt;/strong&gt;, leave every optional log destination unselected, and keep the &lt;strong&gt;Archives&lt;/strong&gt; toggle off. Do not add a resource policy, schema discovery, or tags for this short run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwlddi2vg9sfofnpvdg6n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwlddi2vg9sfofnpvdg6n.png" alt="The custom event-bus form shows the generated name, the AWS owned key, and no optional log destination." width="800" height="753"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form shows &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt;, the AWS owned key, no selected log destination, and the archive toggle off. Only the custom bus is created in this step.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Amazon SQS&lt;/strong&gt;, search for the exact name &lt;code&gt;miruky-gqnaikvhwcffylgd&lt;/code&gt; and stop if it already exists. Otherwise, create a &lt;strong&gt;Standard&lt;/strong&gt; queue with that name. For this short run of synthetic, non-sensitive data, disable the default SQS-managed server-side encryption. EventBridge does not support an SQS target encrypted with an AWS owned key. Leave the remaining delivery settings at their defaults.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjimkemx59w45yhk9zhog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjimkemx59w45yhk9zhog.png" alt="The SQS form shows the generated Standard queue name and server-side encryption disabled." width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The queue form pairs &lt;code&gt;Standard&lt;/code&gt; with &lt;code&gt;miruky-gqnaikvhwcffylgd&lt;/code&gt; and shows server-side encryption disabled. No delivery-setting override or dead-letter queue is added. This is an exercise boundary, not a production recommendation. For an encrypted production target, use a customer managed KMS key and grant EventBridge the required KMS permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Build the rule and its input transformer
&lt;/h2&gt;

&lt;p&gt;Return to EventBridge and choose &lt;strong&gt;Create rule&lt;/strong&gt; on &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt;. The current Console offers an Enhanced builder and an Advanced builder. Choose &lt;strong&gt;Advanced builder&lt;/strong&gt;, enter &lt;code&gt;miruky-imfnycrrfauwoztn&lt;/code&gt;, and confirm the custom event bus. In &lt;strong&gt;Build event pattern&lt;/strong&gt;, choose &lt;strong&gt;Other&lt;/strong&gt; and use this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"demo.orders"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"OrderCreated"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule accepts only the fixed source and detail type used in this run. It does not inspect the individual fields inside &lt;code&gt;detail&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F18enaw0au42p617kjjhv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F18enaw0au42p617kjjhv.png" alt="The EventBridge rule builder shows the custom bus and the two-field event pattern." width="650" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rule builder shows &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt;, &lt;code&gt;demo.orders&lt;/code&gt;, and &lt;code&gt;OrderCreated&lt;/code&gt; together. The pattern is limited to the fixed source and detail type.&lt;/p&gt;

&lt;p&gt;For the target, choose &lt;strong&gt;AWS service&lt;/strong&gt;, &lt;strong&gt;SQS queue&lt;/strong&gt;, and &lt;code&gt;miruky-gqnaikvhwcffylgd&lt;/code&gt;. The current target widget creates an IAM execution role with narrowly scoped permission to send to the selected queue. It does not expose a role-name field, so the Console-generated name differs on every run. I left the queue access policy unchanged and used this execution-role path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2235e7b71nghfqjauk6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2235e7b71nghfqjauk6l.png" alt="The SQS target configuration shows the Console-created execution role without exposing its ARN." width="660" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Execution role&lt;/code&gt; block confirms that EventBridge has an identity-based path to call SQS. The cropped image excludes role and queue ARNs as well as account-specific identifiers.&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;Additional settings&lt;/strong&gt;, choose &lt;strong&gt;Input transformer&lt;/strong&gt;. For the optional sample event, I used the matching synthetic event shown later in this article. Then define these variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.detail.orderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.detail.total"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the variables in this input template. The angle-bracket tokens are EventBridge placeholders. Quote placeholders that must remain JSON strings; leave the numeric placeholder unquoted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "event_time": "&amp;lt;time&amp;gt;",
  "id": "&amp;lt;orderId&amp;gt;",
  "amount": &amp;lt;total&amp;gt;,
  "source": "eventbridge"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The preview keeps &lt;code&gt;time&lt;/code&gt; and &lt;code&gt;orderId&lt;/code&gt; as strings and &lt;code&gt;total&lt;/code&gt; as a number. No JSONPath points to &lt;code&gt;customerNote&lt;/code&gt;, the original event object, the account attribute, or the Region value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F24fhd95qw1nh4tgpy1xi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F24fhd95qw1nh4tgpy1xi.png" alt="The input-transformer dialog shows three JSONPath variables and the four-field output preview." width="780" height="1240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The preview contains &lt;code&gt;event_time&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;amount&lt;/code&gt;, and &lt;code&gt;source&lt;/code&gt; after applying the three JSONPath variables. It contains no customerNote field or original event envelope.&lt;/p&gt;

&lt;p&gt;Finish the wizard and open the rule. Confirm that it is enabled, belongs to the custom bus, and lists the generated SQS queue as its target.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9sju0073mr50pbiecyu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9sju0073mr50pbiecyu5.png" alt="The enabled rule details show the custom event bus and the SQS target without account-specific identifiers." width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rule details show &lt;code&gt;Enabled&lt;/code&gt;, &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt;, and &lt;code&gt;miruky-gqnaikvhwcffylgd&lt;/code&gt;. Account-scoped resource identifiers are excluded from the view.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prove that a nonmatching source does not reach SQS
&lt;/h2&gt;

&lt;p&gt;Give a new or updated EventBridge target a short period to propagate. From &lt;strong&gt;Event buses&lt;/strong&gt;, open the actions for &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt; and choose &lt;strong&gt;Send events&lt;/strong&gt;. Enter source &lt;code&gt;demo.orders.unmatched&lt;/code&gt;, detail type &lt;code&gt;OrderCreated&lt;/code&gt;, and this event detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-control"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerNote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"do-not-forward"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source string is intentionally different from the rule's exact &lt;code&gt;demo.orders&lt;/code&gt; value. The detail type and JSON shape remain valid, which isolates the event-pattern decision to that one field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpeckkbodcn8ml8r59zfz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpeckkbodcn8ml8r59zfz.png" alt="The control-event form uses a source that does not match the rule." width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form shows &lt;code&gt;demo.orders.unmatched&lt;/code&gt;, &lt;code&gt;OrderCreated&lt;/code&gt;, and the synthetic control detail. The source value cannot satisfy the rule's exact &lt;code&gt;demo.orders&lt;/code&gt; array entry.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Send&lt;/strong&gt; and wait for the ingestion confirmation. EventBridge can accept an event on the custom bus even when none of that bus's rules match it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp4tafscatgpmd0bzmixw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp4tafscatgpmd0bzmixw.png" alt="EventBridge confirms ingestion of the deliberately nonmatching control event." width="799" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Console reports that the control event was sent successfully to &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt;. That confirmation proves ingestion only; it does not claim that the SQS target ran.&lt;/p&gt;

&lt;p&gt;Wait 60 seconds, then open the queue's &lt;strong&gt;Send and receive messages&lt;/strong&gt; page. Set the polling duration to 20 seconds and complete two polls. The queue started empty, so both polls should return no message for this source value. A single immediate empty response would be weaker evidence because delivery and Console updates are asynchronous.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bk6ryzxblru4ypbv317.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bk6ryzxblru4ypbv317.png" alt="The SQS poll returns no message after the nonmatching event is accepted." width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both long polls return no available message after the accepted control event. Together with EventBridge's exact string matching, the result establishes the controlled failure: the event reached the bus, but the source mismatch prevented this rule from invoking SQS.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Send a matching event and inspect the transformed body
&lt;/h2&gt;

&lt;p&gt;Return to &lt;strong&gt;Send events&lt;/strong&gt;. Enter source &lt;code&gt;demo.orders&lt;/code&gt;, detail type &lt;code&gt;OrderCreated&lt;/code&gt;, and this event detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerNote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"do-not-forward"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three values are synthetic. The extra note provides a visible negative control: it belongs to the source event but should not cross the transformer boundary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3l1o88qjecevj4df0xpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3l1o88qjecevj4df0xpz.png" alt="The custom-event form contains the fixed source, detail type, and synthetic order detail." width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form shows &lt;code&gt;demo.orders&lt;/code&gt;, &lt;code&gt;OrderCreated&lt;/code&gt;, &lt;code&gt;order-001&lt;/code&gt;, &lt;code&gt;42&lt;/code&gt;, and &lt;code&gt;do-not-forward&lt;/code&gt;. The unwanted note is present at the source boundary before transformation.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Send&lt;/strong&gt; and wait for the success confirmation. A successful &lt;code&gt;PutEvents&lt;/code&gt; result means EventBridge accepted the event; the SQS receive is the separate delivery proof.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh68i04309o4ud8ejt0mx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh68i04309o4ud8ejt0mx.png" alt="EventBridge confirms that the custom event was sent to the generated event bus." width="799" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Console reports that the event was sent successfully to &lt;code&gt;miruky-lddnesdcjdgblgrp&lt;/code&gt;. This confirms ingestion, not delivery to SQS.&lt;/p&gt;

&lt;p&gt;Open the SQS queue, choose &lt;strong&gt;Send and receive messages&lt;/strong&gt;, and poll for messages. The delivered body should have exactly four top-level keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EVENTBRIDGE_GENERATED_TIME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eventbridge"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The observed &lt;code&gt;event_time&lt;/code&gt; is an ISO 8601 value carried by the EventBridge event. Verify that the body does not contain &lt;code&gt;customerNote&lt;/code&gt;, &lt;code&gt;account&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;resources&lt;/code&gt;, &lt;code&gt;detail-type&lt;/code&gt;, or the original EventBridge event ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6hp4n0fee8lt1hw233as.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6hp4n0fee8lt1hw233as.png" alt="The received SQS body contains the four transformed fields and omits the original event envelope." width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cropped body contains &lt;code&gt;event_time&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;amount&lt;/code&gt;, and &lt;code&gt;source&lt;/code&gt;, with &lt;code&gt;order-001&lt;/code&gt;, &lt;code&gt;42&lt;/code&gt;, and &lt;code&gt;eventbridge&lt;/code&gt; as the fixed values. It does not contain customerNote, do-not-forward, account, region, resources, or detail-type.&lt;/p&gt;

&lt;p&gt;That body is the useful distinction: the rule matched the original event, but SQS received the template output instead of the complete envelope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The nonmatching control event reached the custom bus but produced no SQS message. The matching event carried three detail values plus the EventBridge envelope; the rule extracted &lt;code&gt;orderId&lt;/code&gt;, &lt;code&gt;total&lt;/code&gt;, and &lt;code&gt;time&lt;/code&gt;, added a fixed source label, and delivered a four-key JSON body to SQS. The unwanted note and account-scoped envelope attributes never appeared in the target payload.&lt;/p&gt;

&lt;p&gt;Input transformers are helpful when a target needs a small, stable contract rather than every field produced upstream. They do not validate missing JSONPath values when the rule is created, so the source shape and transformed output still need an end-to-end test like this one.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html" rel="noopener noreferrer"&gt;Amazon EventBridge input transformation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-input-rule.html" rel="noopener noreferrer"&gt;Configuring an input transformer when creating a rule&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-input-transformer-tutorial.html" rel="noopener noreferrer"&gt;Tutorial: Use input transformers to transform events in EventBridge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-pattern.html" rel="noopener noreferrer"&gt;Event pattern syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-event-bus.html" rel="noopener noreferrer"&gt;Creating an event bus in Amazon EventBridge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-targets.html" rel="noopener noreferrer"&gt;Event bus targets in Amazon EventBridge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-iam-roles.html" rel="noopener noreferrer"&gt;IAM roles for sending events to targets in Amazon EventBridge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-visual.html" rel="noopener noreferrer"&gt;Creating rules using the Enhanced Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-sqs-sse-queue.html" rel="noopener noreferrer"&gt;Configuring SQS-managed server-side encryption&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/eventbridge/pricing/" rel="noopener noreferrer"&gt;Amazon EventBridge pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/sqs/pricing/" rel="noopener noreferrer"&gt;Amazon SQS pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>eventbridge</category>
      <category>sqs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Prove FIFO Deduplication in Amazon SQS from the AWS Console</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Fri, 04 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/prove-fifo-deduplication-in-amazon-sqs-from-the-aws-console-24jn</link>
      <guid>https://dev.to/miruky/prove-fifo-deduplication-in-amazon-sqs-from-the-aws-console-24jn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;An Amazon SQS FIFO queue tracks &lt;code&gt;MessageDeduplicationId&lt;/code&gt; values for a five-minute interval. When a producer repeats an ID during that window, SQS acknowledges the later send but does not make another copy available to consumers.&lt;/p&gt;

&lt;p&gt;This Console run disables content-based deduplication so the IDs are explicit. It sends two different bodies with &lt;code&gt;dedup-001&lt;/code&gt;, adds a control body with &lt;code&gt;dedup-002&lt;/code&gt;, and then proves that consumers receive the original body and the control body—but not the duplicate body.&lt;/p&gt;

&lt;p&gt;The validation uses one encrypted FIFO queue and a few SQS requests. Current SQS request pricing and free-tier terms are documented on the linked pricing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a FIFO queue with explicit deduplication IDs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpm4lhhz0ua7lid6ucyms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpm4lhhz0ua7lid6ucyms.png" alt="The English AWS Console shows United States (N. Virginia) before creating the FIFO queue." width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header confirms &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the SQS Console is in English. &lt;code&gt;us-east-1&lt;/code&gt; is the fixed Region for this run.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;Amazon SQS&lt;/strong&gt; and search for &lt;code&gt;miruky-fibqomwvgyqpoceu.fifo&lt;/code&gt;. The exact-name result should be empty before the queue is created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhr9izapa8ckx8jqfu4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhr9izapa8ckx8jqfu4u.png" alt="The SQS queue list has no exact match for the generated FIFO queue name." width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact filter for &lt;code&gt;miruky-fibqomwvgyqpoceu.fifo&lt;/code&gt; returns no queue. That empty result establishes the resource boundary before creation.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create queue&lt;/strong&gt;, select &lt;strong&gt;FIFO&lt;/strong&gt;, and enter &lt;code&gt;miruky-fibqomwvgyqpoceu.fifo&lt;/code&gt;. Keep content-based deduplication disabled, use the default FIFO throughput settings, set the receive-message wait time to 20 seconds, and retain SQS-managed server-side encryption.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2nnufh03af696f9omxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2nnufh03af696f9omxs.png" alt="The create-queue form shows the generated dot-fifo name, explicit deduplication mode, and long polling." width="800" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form shows &lt;code&gt;FIFO&lt;/code&gt;, &lt;code&gt;miruky-fibqomwvgyqpoceu.fifo&lt;/code&gt;, disabled content-based deduplication, and a receive wait of 20 seconds. Each send must therefore provide a deduplication ID, while each final empty receive uses long polling rather than the default short-poll sample.&lt;/p&gt;

&lt;p&gt;Open the new queue and verify that its type is FIFO, content-based deduplication is off, and the receive wait time is 20 seconds. The &lt;code&gt;.fifo&lt;/code&gt; suffix is required by SQS and is recorded separately from the generated random name core in the resource ledger.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fea8sw7pjwecmhy45hv0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fea8sw7pjwecmhy45hv0s.png" alt="The queue details show FIFO type, managed encryption, and content-based deduplication disabled." width="800" height="829"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The queue details retain &lt;code&gt;FIFO&lt;/code&gt;, disabled content-based deduplication, a receive wait of 20 seconds, and the SQS-managed encryption setting. The send comparison can now depend on explicit IDs rather than body hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Send an original message and its duplicate
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Send and receive messages&lt;/strong&gt;. Enter the body &lt;code&gt;accepted-original&lt;/code&gt;, group ID &lt;code&gt;group-001&lt;/code&gt;, and deduplication ID &lt;code&gt;dedup-001&lt;/code&gt;, then send it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4gfoqstc2o3an6x7j63w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4gfoqstc2o3an6x7j63w.png" alt="The original FIFO message uses group-001 and dedup-001." width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The original send form contains &lt;code&gt;accepted-original&lt;/code&gt;, &lt;code&gt;group-001&lt;/code&gt;, and &lt;code&gt;dedup-001&lt;/code&gt;. These values establish the message and its deduplication identity.&lt;/p&gt;

&lt;p&gt;SQS should report the send as successful. Keep the message in the queue and send another message before the five-minute deduplication interval expires.&lt;/p&gt;

&lt;p&gt;For the next send, use the different body &lt;code&gt;accepted-duplicate&lt;/code&gt; but keep both &lt;code&gt;group-001&lt;/code&gt; and &lt;code&gt;dedup-001&lt;/code&gt;. Because content-based deduplication is disabled and the bodies differ, the repeated explicit ID is the matching input in this comparison.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs267n4l9qkfr2997ih9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs267n4l9qkfr2997ih9f.png" alt="The duplicate-ID send uses a different body but repeats dedup-001 within five minutes." width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The duplicate-ID form changes the body to &lt;code&gt;accepted-duplicate&lt;/code&gt; while retaining &lt;code&gt;group-001&lt;/code&gt; and &lt;code&gt;dedup-001&lt;/code&gt;. The repeated explicit ID is the only deduplication match between the two sends.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Send message&lt;/strong&gt; and observe another success confirmation. The successful acknowledgement means SQS accepted the API request; it does not mean that another deliverable message was added.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdcdqyf2wmpu81vlt1vrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdcdqyf2wmpu81vlt1vrv.png" alt="The Console acknowledges the duplicate-ID send without exposing its generated message ID." width="800" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Console reports that the duplicate-ID request was sent and is ready to be received. Its service-generated message ID is excluded because the acknowledgement alone is the evidence needed here.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Add a control message with a new ID
&lt;/h2&gt;

&lt;p&gt;Send one more message with body &lt;code&gt;accepted-control&lt;/code&gt;, group ID &lt;code&gt;group-001&lt;/code&gt;, and deduplication ID &lt;code&gt;dedup-002&lt;/code&gt;. This ID has not appeared in the five-minute window, so the control message should be available after the original.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5eu77zzrzc639t5652w8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5eu77zzrzc639t5652w8.png" alt="The control message uses the same group and the new deduplication ID dedup-002." width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The control form contains &lt;code&gt;accepted-control&lt;/code&gt;, &lt;code&gt;group-001&lt;/code&gt;, and &lt;code&gt;dedup-002&lt;/code&gt;. Its fresh ID provides the message that should remain deliverable after the original.&lt;/p&gt;

&lt;p&gt;All three send operations should have returned success. The expected deliverable set still contains only two bodies because the two &lt;code&gt;dedup-001&lt;/code&gt; requests represent one deduplication identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Receive the deliverable messages
&lt;/h2&gt;

&lt;p&gt;Use 10 as the batch ceiling and start polling. In this validation, both deliverable messages arrived as two rows during one polling session; the session lasted 30 seconds. If only the original appears, delete it and poll again so the next message in the same group becomes available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdzhfpaje0ny46nyywtb2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdzhfpaje0ny46nyywtb2.png" alt="A receive shows accepted-original with dedup-001 in group-001." width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cropped receive view shows &lt;code&gt;accepted-original&lt;/code&gt;, &lt;code&gt;group-001&lt;/code&gt;, and &lt;code&gt;dedup-001&lt;/code&gt;. No service-generated identifier or receipt value is retained.&lt;/p&gt;

&lt;p&gt;Inspect only the body, message group ID, and deduplication ID. Do not publish the service-generated message ID, sequence number, receipt handle, timestamps, queue URL, or queue ARN.&lt;/p&gt;

&lt;p&gt;Open the other row and confirm that its body is &lt;code&gt;accepted-control&lt;/code&gt;. The expected detail values are &lt;code&gt;group-001&lt;/code&gt; and &lt;code&gt;dedup-002&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq5rmxzt08ezivrn4kl9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq5rmxzt08ezivrn4kl9k.png" alt="The next deliverable message is accepted-control with the unique dedup-002 ID." width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next cropped receive view shows &lt;code&gt;accepted-control&lt;/code&gt;, &lt;code&gt;group-001&lt;/code&gt;, and &lt;code&gt;dedup-002&lt;/code&gt;. This is the unique-ID control message expected after the original.&lt;/p&gt;

&lt;p&gt;Delete both rows while their current receipt handles are valid. A receipt handle belongs to one receive action, and a later receive returns a new one. If the 30-second visibility timeout expires while you inspect the details, poll again and delete the two rows immediately.&lt;/p&gt;

&lt;p&gt;After deletion, run two complete polling sessions. The displayed polling duration is 30 seconds and the maximum message count is 10, while the queue's receive-message wait remains 20 seconds. Two completed sessions are required, both with zero available messages and zero rows. The suppressed body must remain absent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw7sd7593sivt7zx1uchx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw7sd7593sivt7zx1uchx.png" alt="The final poll returns no message after the original and control are deleted." width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final completed polling session shows no available message and no message row. The preceding completed session produced the same zero-message result, and &lt;code&gt;accepted-duplicate&lt;/code&gt; never appeared. The queue's 20-second receive wait enables long polling, which queries all SQS servers and reduces the false-empty behavior of short polling.&lt;/p&gt;

&lt;p&gt;The received bodies are the proof set: &lt;code&gt;accepted-original&lt;/code&gt; and &lt;code&gt;accepted-control&lt;/code&gt;. The repeated &lt;code&gt;dedup-001&lt;/code&gt; request was acknowledged during sending but suppressed from delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The run produced two deliverable bodies from three acknowledged sends. The repeated identifier suppressed the later body, while the fresh identifier admitted the control.&lt;/p&gt;

&lt;p&gt;SQS keeps tracking a deduplication ID even after its message is received and deleted. Outside the five-minute window, or when producer acknowledgement handling is uncertain for longer than that window, consumers should still use an application-level idempotency strategy.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagededuplicationid-property.html" rel="noopener noreferrer"&gt;Using the message deduplication ID in Amazon SQS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-fifo-queue-message-identifiers.html" rel="noopener noreferrer"&gt;FIFO queue and message identifiers in Amazon SQS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html" rel="noopener noreferrer"&gt;SendMessage API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues-exactly-once-processing.html" rel="noopener noreferrer"&gt;Exactly-once processing in Amazon SQS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues-understanding-logic.html" rel="noopener noreferrer"&gt;FIFO queue delivery logic in Amazon SQS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html" rel="noopener noreferrer"&gt;Amazon SQS short and long polling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/creating-sqs-fifo-queues.html" rel="noopener noreferrer"&gt;Creating an Amazon SQS FIFO queue and sending a message&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/sqs/pricing/" rel="noopener noreferrer"&gt;Amazon SQS pricing&lt;/a&gt;
``&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>sqs</category>
      <category>eventdriven</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Trace a Lambda Invocation with AWS X-Ray</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Thu, 03 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/trace-a-lambda-invocation-with-aws-x-ray-38mg</link>
      <guid>https://dev.to/miruky/trace-a-lambda-invocation-with-aws-x-ray-38mg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;AWS Lambda can emit service-level AWS X-Ray segments without application instrumentation. Active tracing records the Lambda service's work and the function execution as separate segments, which gives a small but useful trace map for a synchronous invocation.&lt;/p&gt;

&lt;p&gt;This Console run creates a Python function with a fixed response, enables Lambda service traces, invokes the same empty private event three times, and inspects one sampled trace in CloudWatch. The article does not open raw trace data or publish trace IDs, request IDs, ARNs, account details, environment variables, annotations, or metadata.&lt;/p&gt;

&lt;p&gt;X-Ray sampling and trace storage have current pricing terms, as do Lambda invocations and CloudWatch Logs. Review the linked pricing pages before enabling tracing on production traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a tracing-capable execution role
&lt;/h2&gt;

&lt;p&gt;The validation stays in &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffoinwq0gewnsqngmjnpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffoinwq0gewnsqngmjnpe.png" alt="The English AWS Console shows United States (N. Virginia) before the tracing exercise." width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header confirms &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the Lambda Console is in English. This fixes the Region shared by the function and its trace data.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;IAM&lt;/strong&gt;, choose &lt;strong&gt;Roles&lt;/strong&gt;, and create a role for the &lt;strong&gt;Lambda&lt;/strong&gt; service. Enter &lt;code&gt;miruky-eftzwycpzwhjwahl&lt;/code&gt; and attach &lt;code&gt;AWSLambdaBasicExecutionRole&lt;/code&gt; plus &lt;code&gt;AWSXRayDaemonWriteAccess&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg05lr125xwx8ell1wxys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg05lr125xwx8ell1wxys.png" alt="The IAM role review shows the generated name and the two required managed policies." width="800" height="839"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The review shows &lt;code&gt;miruky-eftzwycpzwhjwahl&lt;/code&gt;, &lt;code&gt;AWSLambdaBasicExecutionRole&lt;/code&gt;, and &lt;code&gt;AWSXRayDaemonWriteAccess&lt;/code&gt; together. The policies cover Lambda log delivery, trace upload, and the sampling-rule reads needed by X-Ray; they do not grant X-Ray configuration writes.&lt;/p&gt;

&lt;p&gt;Open the role and verify its Lambda trust relationship and the two attached policies. The X-Ray policy grants &lt;code&gt;xray:PutTraceSegments&lt;/code&gt; and &lt;code&gt;xray:PutTelemetryRecords&lt;/code&gt;; the basic policy covers CloudWatch Logs writes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl16z7v9yakw2t3s0q62v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl16z7v9yakw2t3s0q62v.png" alt="Stacked crops from the same IAM role workflow show the two managed policies and the Lambda trust relationship." width="799" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The stacked crops show the final permissions review and the saved trust relationship. The role has exactly the two intended managed policies, and &lt;code&gt;lambda.amazonaws.com&lt;/code&gt; can assume it. &lt;code&gt;AWSXRayDaemonWriteAccess&lt;/code&gt; permits segment and telemetry uploads plus the three sampling read operations documented by its current policy version.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create a function with deterministic output
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Lambda&lt;/strong&gt; and search for the exact name &lt;code&gt;miruky-cokynoojzfivdjpl&lt;/code&gt;. The result should be empty before this validation creates the function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fblbcmo983yfgldoc4p5k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fblbcmo983yfgldoc4p5k.png" alt="The Lambda function list has no exact match for the generated function name." width="799" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact filter for &lt;code&gt;miruky-cokynoojzfivdjpl&lt;/code&gt; returns no function. That empty result establishes the resource boundary before creation.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create function&lt;/strong&gt; and &lt;strong&gt;Author from scratch&lt;/strong&gt;. Enter &lt;code&gt;miruky-cokynoojzfivdjpl&lt;/code&gt;, select &lt;strong&gt;Python 3.14&lt;/strong&gt; and &lt;code&gt;x86_64&lt;/code&gt;, then choose the existing role &lt;code&gt;miruky-eftzwycpzwhjwahl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4qyz7fgxpge00ohsszyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4qyz7fgxpge00ohsszyi.png" alt="Stacked crops from the same create-function form show the generated name, Python 3.14, x86_64 default, and existing role." width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form shows &lt;code&gt;miruky-cokynoojzfivdjpl&lt;/code&gt;, &lt;code&gt;Python 3.14&lt;/code&gt;, &lt;code&gt;x86_64&lt;/code&gt;, and &lt;code&gt;miruky-eftzwycpzwhjwahl&lt;/code&gt; together. These visible values fix the runtime and execution role before creation.&lt;/p&gt;

&lt;p&gt;Replace the sample code with the fixed handler below and choose &lt;strong&gt;Deploy&lt;/strong&gt;. It reads no event field and returns no runtime, request, environment, or identity value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Return a fixed value so tracing adds no application-data variability.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traced&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz4e53z29kavnnon1w3if.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz4e53z29kavnnon1w3if.png" alt="The deployed Lambda code returns only the fixed traced status." width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The deployed editor contains only the deterministic handler and its fixed &lt;code&gt;traced&lt;/code&gt; value. The trace experiment therefore adds no application instrumentation or dynamic response data.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Enable Lambda service traces
&lt;/h2&gt;

&lt;p&gt;Before changing the function, open &lt;strong&gt;Configuration&lt;/strong&gt;, choose &lt;strong&gt;Monitoring and operations tools&lt;/strong&gt;, and edit &lt;strong&gt;Additional monitoring tools&lt;/strong&gt;. Inspect the &lt;strong&gt;Lambda service traces&lt;/strong&gt; control before selecting it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzga0mrhv9rfsb3vr65r2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzga0mrhv9rfsb3vr65r2.png" alt="The monitoring editor shows Lambda service traces unchecked before the change." width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Lambda service traces&lt;/code&gt; control is unchecked. The handler can still run in this state, but Lambda will not emit the two service-level X-Ray segments that this exercise needs to inspect.&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;CloudWatch Application Signals and AWS X-Ray&lt;/strong&gt;, enable &lt;strong&gt;Lambda service traces&lt;/strong&gt; and save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuhhxadxtx5pnt9vk3ltc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuhhxadxtx5pnt9vk3ltc.png" alt="The monitoring editor enables Lambda service traces for the function." width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The editor shows &lt;code&gt;Lambda service traces&lt;/code&gt; enabled under the X-Ray controls. Saving that choice changes the function from pass-through behavior to active tracing.&lt;/p&gt;

&lt;p&gt;Return to the monitoring configuration and confirm that tracing is active. Lambda now creates service-level segments automatically; this step does not add custom application subsegments, annotations, or metadata.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1upmckndas05swlf72u5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1upmckndas05swlf72u5.png" alt="The saved monitoring configuration shows active Lambda service tracing." width="799" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The saved configuration continues to show &lt;code&gt;Lambda service traces&lt;/code&gt; enabled. The setting is persisted before any validation invocation is run.&lt;/p&gt;

&lt;p&gt;AWS places the legacy X-Ray SDKs in maintenance mode and recommends OpenTelemetry for new custom instrumentation. This article uses only Lambda's built-in service tracing, so no X-Ray SDK dependency is bundled with the function.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Invoke a private event and find the sampled trace
&lt;/h2&gt;

&lt;p&gt;Open the &lt;strong&gt;Test&lt;/strong&gt; tab and create a private event named &lt;code&gt;miruky-rnsdepoalbavxzhr&lt;/code&gt; with the JSON body &lt;code&gt;{}&lt;/code&gt;. Save it and run the same event three times, leaving at least one second between invocations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgz7tonolcumx1u3xtmsv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgz7tonolcumx1u3xtmsv.png" alt="The private test event uses the generated name and an empty JSON object." width="799" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The event editor shows &lt;code&gt;miruky-rnsdepoalbavxzhr&lt;/code&gt;, &lt;code&gt;Private&lt;/code&gt;, and the empty object &lt;code&gt;{}&lt;/code&gt;. Repeating this fixed event avoids adding attributes that could appear in trace details.&lt;/p&gt;

&lt;p&gt;Each Console test should return &lt;code&gt;{"status": "traced"}&lt;/code&gt;. X-Ray samples requests rather than recording every invocation; Lambda's documented sampling uses one request per second plus 5 percent of additional requests, and that sampling rate cannot be configured for Lambda functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwi9h9wdkpdkew0wnb0a2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwi9h9wdkpdkew0wnb0a2.png" alt="A synchronous Console test succeeds with the fixed traced response." width="799" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cropped result shows a successful invocation and &lt;code&gt;{"status": "traced"}&lt;/code&gt;. Execution logs and request identifiers remain outside the image.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;CloudWatch&lt;/strong&gt;, choose &lt;strong&gt;X-Ray traces&lt;/strong&gt;, and then choose &lt;strong&gt;Traces&lt;/strong&gt;. Under &lt;strong&gt;Query refiners&lt;/strong&gt;, keep &lt;strong&gt;Node&lt;/strong&gt; selected. The generated function appears twice: once as &lt;strong&gt;Lambda Context&lt;/strong&gt; and once as &lt;strong&gt;Lambda Function&lt;/strong&gt;. Select &lt;strong&gt;Lambda Function&lt;/strong&gt;, add it to the query, and run the query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbjr7y6ulphi1pnaast15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbjr7y6ulphi1pnaast15.png" alt="The X-Ray query refiners show the Lambda Context and Lambda Function nodes, with three traces retrieved." width="799" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The refiners show &lt;code&gt;miruky-cokynoojzfivdjpl&lt;/code&gt; as both node types, and the query returns three traces from the validation window. The generated query includes an &lt;code&gt;account.id&lt;/code&gt; condition, so that query text and every trace identifier stay outside the retained image.&lt;/p&gt;

&lt;p&gt;Open one row from the trace table without copying its trace ID.&lt;/p&gt;

&lt;p&gt;The trace map should contain two Lambda nodes. The current map labels them &lt;strong&gt;Lambda Context&lt;/strong&gt; and &lt;strong&gt;Lambda Function&lt;/strong&gt;. In the segment timeline, those nodes correspond to &lt;code&gt;AWS::Lambda&lt;/code&gt; for service work such as preparing the execution environment and &lt;code&gt;AWS::Lambda::Function&lt;/code&gt; for work performed by the function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvje3ujpr5fcjnkp9cw6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvje3ujpr5fcjnkp9cw6.png" alt="The trace map shows separate Lambda Context and Lambda Function nodes." width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The map visibly separates &lt;strong&gt;Lambda Context&lt;/strong&gt; from &lt;strong&gt;Lambda Function&lt;/strong&gt;. Both nodes belong to the selected invocation, but they represent different portions of its execution.&lt;/p&gt;

&lt;p&gt;Open the segment timeline and inspect the status and duration columns. I kept the raw segment JSON closed because it contains unique IDs, timestamps, resource references, and other details that this article does not need.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feof9oiqk4akg6anqq7uz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feof9oiqk4akg6anqq7uz.png" alt="The segment timeline shows successful service and function segments with their durations." width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The timeline shows &lt;code&gt;AWS::Lambda&lt;/code&gt; and &lt;code&gt;AWS::Lambda::Function&lt;/code&gt; with &lt;code&gt;OK&lt;/code&gt; status and visible durations. The Logs section remains outside the image, and trace, segment, resource, and request identifiers are not retained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;One synchronous test produced a trace with two different operational views: Lambda's service-side preparation and the function's own execution. The separation helps distinguish platform work from handler work before custom instrumentation is added.&lt;/p&gt;

&lt;p&gt;For a new distributed application, use ADOT and OpenTelemetry when you need custom spans or multiple observability backends. Keep indexed attributes low-cardinality, avoid putting personal or secret data into trace attributes, and tune sampling against diagnostic value and cost.&lt;/p&gt;

&lt;p&gt;X-Ray retains trace data for 30 days. Removing the function stops new traces, but an already recorded validation trace follows that service-controlled retention period.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html" rel="noopener noreferrer"&gt;Visualize Lambda function invocations using AWS X-Ray&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html#services-xray-concepts" rel="noopener noreferrer"&gt;Understanding Lambda X-Ray traces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-console-traces.html" rel="noopener noreferrer"&gt;Viewing traces and trace details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-console-servicemap.html" rel="noopener noreferrer"&gt;Using the X-Ray trace map&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html" rel="noopener noreferrer"&gt;AWS X-Ray concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-migration.html" rel="noopener noreferrer"&gt;Migrating from X-Ray instrumentation to OpenTelemetry instrumentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/cloudwatch/pricing/" rel="noopener noreferrer"&gt;Amazon CloudWatch pricing, including X-Ray tracing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;AWS Lambda pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>xray</category>
      <category>observability</category>
    </item>
    <item>
      <title>Alarm on an Amazon SQS Backlog with CloudWatch</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Wed, 02 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/alarm-on-an-amazon-sqs-backlog-with-cloudwatch-5786</link>
      <guid>https://dev.to/miruky/alarm-on-an-amazon-sqs-backlog-with-cloudwatch-5786</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt; reports how many Amazon SQS messages are available for consumers to retrieve. A rising value is a practical backlog signal when producers are outpacing consumers or no consumer is running.&lt;/p&gt;

&lt;p&gt;This Console run creates a Standard queue and a CloudWatch metric alarm with a one-minute period. One queued payload moves the alarm from OK to In alarm; receiving and deleting it returns the metric below the threshold and the alarm to OK.&lt;/p&gt;

&lt;p&gt;SQS publishes its standard queue metrics to CloudWatch without a separate metric charge. CloudWatch alarms and SQS requests have their own current pricing terms, so check both pricing pages before applying the settings outside this small validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create and activate a Standard queue
&lt;/h2&gt;

&lt;p&gt;This validation uses &lt;code&gt;us-east-1&lt;/code&gt;, whose Region display name is &lt;strong&gt;United States (N. Virginia)&lt;/strong&gt;. The SQS queue, metric, and alarm all remain there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4dkg3gsu8c9ifdfrn7yf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4dkg3gsu8c9ifdfrn7yf.png" alt="The English AWS Console shows United States (N. Virginia) before creating the queue." width="797" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header confirms &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the SQS Console is in English. This fixes the Region shared by the queue, metric, and alarm.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;Amazon SQS&lt;/strong&gt; and search for the exact generated name &lt;code&gt;miruky-aaptabgtgqjvqmsx&lt;/code&gt;. The result should be empty before this validation creates the queue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6vxuuxaekqzap7atetbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6vxuuxaekqzap7atetbz.png" alt="The SQS queue list has no exact match for the generated queue name." width="800" height="129"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact filter for &lt;code&gt;miruky-aaptabgtgqjvqmsx&lt;/code&gt; returns no queue. That empty result establishes the resource boundary before creation.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create queue&lt;/strong&gt;, select &lt;strong&gt;Standard&lt;/strong&gt;, enter &lt;code&gt;miruky-aaptabgtgqjvqmsx&lt;/code&gt;, and keep the default delivery settings and SQS-managed server-side encryption.&lt;/p&gt;

&lt;p&gt;The next image vertically stacks the type-and-name area with the encryption area from the same create-queue form. Nothing between those two areas changes the settings shown here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm9ndcb4wgllj2gzecs0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm9ndcb4wgllj2gzecs0n.png" alt="The create-queue form shows the generated name, Standard type, and managed encryption." width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form visibly pairs &lt;code&gt;Standard&lt;/code&gt; with &lt;code&gt;miruky-aaptabgtgqjvqmsx&lt;/code&gt; and the default SQS-managed encryption. No delivery setting or dead-letter queue is added.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;Send and receive messages&lt;/strong&gt;, send the fixed body &lt;strong&gt;metric-seed&lt;/strong&gt;, then receive and delete it. These operations activate the queue and provide CloudWatch with an empty baseline before the alarm experiment begins.&lt;/p&gt;

&lt;p&gt;The next image stacks the identifier-excluded body crop with the deletion result from that same controlled receive. Message identifiers and receipt details are excluded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgujot8c4wgmuz6z9jmju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgujot8c4wgmuz6z9jmju.png" alt="The queue receives the metric-seed message before it is deleted from the baseline." width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The received message body is exactly &lt;code&gt;metric-seed&lt;/code&gt;. Deleting it leaves the active queue with no visible backlog before the alarm is created.&lt;/p&gt;

&lt;p&gt;SQS publishes metrics for active queues at one-minute intervals. A newly activated queue can take up to 15 minutes to appear in CloudWatch, so wait for the queue dimension rather than recreating the resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Select the visible-message metric
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;CloudWatch&lt;/strong&gt;, choose &lt;strong&gt;All metrics&lt;/strong&gt;, select &lt;strong&gt;SQS&lt;/strong&gt;, and then choose &lt;strong&gt;Queue Metrics&lt;/strong&gt;. Search for &lt;code&gt;miruky-aaptabgtgqjvqmsx&lt;/code&gt; and select &lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg3zpy6svmt6z9goyhob0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg3zpy6svmt6z9goyhob0.png" alt="CloudWatch Queue Metrics selects ApproximateNumberOfMessagesVisible for the generated queue." width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The selected row combines &lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt; with &lt;code&gt;miruky-aaptabgtgqjvqmsx&lt;/code&gt;. This binds the alarm to the generated queue's visible backlog.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Select metric&lt;/strong&gt;. Set the statistic to &lt;strong&gt;Maximum&lt;/strong&gt; and the period to &lt;strong&gt;1 minute&lt;/strong&gt;; a maximum of at least one means a message was available for retrieval during that period.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c9qo73ftt0uyl8snsbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c9qo73ftt0uyl8snsbu.png" alt="The metric configuration uses Maximum with a one-minute period." width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The metric settings show &lt;code&gt;Maximum&lt;/code&gt; and &lt;code&gt;1 minute&lt;/code&gt;. That combination records each period's observed maximum queue depth and therefore detects any period that reached at least one.&lt;/p&gt;

&lt;p&gt;Use a static threshold and choose &lt;strong&gt;Greater/Equal&lt;/strong&gt; with threshold &lt;code&gt;1&lt;/code&gt;. Under &lt;strong&gt;Additional configuration&lt;/strong&gt;, set &lt;strong&gt;Datapoints to alarm&lt;/strong&gt; to &lt;code&gt;1 out of 2&lt;/code&gt; and choose &lt;strong&gt;Treat missing data as good (not breaching threshold)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The next image stacks the condition area with &lt;strong&gt;Additional configuration&lt;/strong&gt; from the same alarm wizard page. No setting between those two areas changes the values shown here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5xcgzx8f6roqdhd6lpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5xcgzx8f6roqdhd6lpa.png" alt="The alarm condition requires one of two periods at or above one visible message." width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The condition shows &lt;code&gt;Greater/Equal&lt;/code&gt;, threshold &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;1 out of 2&lt;/code&gt;, and missing data treated as good. These four values determine both the trigger and recovery windows.&lt;/p&gt;

&lt;p&gt;The M-out-of-N setting can react to one breaching minute without depending on a single one-minute evaluation window. Treating missing data as not breaching also prevents an inactive queue from becoming an alarm solely because SQS stopped publishing metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Create the alarm without an external action
&lt;/h2&gt;

&lt;p&gt;On the notification step, remove the alarm action for this validation. The article observes state changes in CloudWatch and does not create an SNS topic or send a notification to an email address.&lt;/p&gt;

&lt;p&gt;The next image stacks the empty notification card with the final review's &lt;strong&gt;No actions&lt;/strong&gt; result from the same alarm configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffyrt9zh33ril4vt2k1ni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffyrt9zh33ril4vt2k1ni.png" alt="The notification step has no alarm action configured." width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The notification step shows no configured action. This keeps the validation limited to the alarm state and avoids creating a topic or contacting an address.&lt;/p&gt;

&lt;p&gt;Enter the generated alarm name &lt;code&gt;miruky-trzxgmdimxfnpnvk&lt;/code&gt;, leave the description empty, review the metric and threshold, and create the alarm.&lt;/p&gt;

&lt;p&gt;The next image stacks the metric, condition, and alarm-name sections from that single final review page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qc5lkuxhnjnwuhv5917.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qc5lkuxhnjnwuhv5917.png" alt="The alarm review shows the generated name and visible-message condition." width="800" height="952"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The review shows &lt;code&gt;miruky-trzxgmdimxfnpnvk&lt;/code&gt; with the visible-message metric and fixed threshold. No action is listed in the final configuration.&lt;/p&gt;

&lt;p&gt;Open the alarm and wait until its state is &lt;code&gt;OK&lt;/code&gt;. A newly created alarm may initially be unevaluated while CloudWatch gathers its first result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjr7xldedbad01inlhtn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjr7xldedbad01inlhtn.png" alt="The new CloudWatch alarm reaches OK while the queue has no visible messages." width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The alarm state reaches &lt;code&gt;OK&lt;/code&gt; while the queue has no visible message. This is the baseline before the backlog message is sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Trigger the backlog alarm
&lt;/h2&gt;

&lt;p&gt;Return to the queue and send one message with the body &lt;code&gt;backlog-demo&lt;/code&gt;. Leave it unreceived for this step; receiving changes its state from available to in flight, so it no longer contributes to this backlog metric.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F077kjasksqvbj3esfazc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F077kjasksqvbj3esfazc.png" alt="The SQS Console sends one fixed backlog-demo message to the queue." width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The send form and confirmation identify the fixed body &lt;code&gt;backlog-demo&lt;/code&gt;. Leaving it unreceived keeps it in the visible-message metric.&lt;/p&gt;

&lt;p&gt;Wait for the next one-minute metric publication and CloudWatch evaluation. The alarm should move to &lt;code&gt;In alarm&lt;/code&gt; after at least one of its two evaluated periods has a maximum visible count of 1 or more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fly2hyxt4rrby1flxl91t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fly2hyxt4rrby1flxl91t.png" alt="The CloudWatch alarm is In alarm after the queue reports one visible message." width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The alarm state shows &lt;code&gt;In alarm&lt;/code&gt; while the graph contains a breaching visible-message point. This is the first observed transition driven by queue backlog.&lt;/p&gt;

&lt;p&gt;The SQS metric is approximate because the service is distributed, so it is appropriate for operational decisions rather than an exact transactional count. A production threshold should reflect normal arrival rate, consumer capacity, and the time a backlog can remain without violating a service objective.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Process the message and watch recovery
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Send and receive messages&lt;/strong&gt;, poll for &lt;code&gt;backlog-demo&lt;/code&gt;, select it, and choose &lt;strong&gt;Delete&lt;/strong&gt;. Receiving moves the queued item into its visibility-timeout window; deletion prevents it from returning afterward.&lt;/p&gt;

&lt;p&gt;The next image stacks the identifier-excluded body crop with the deletion result from that same controlled receive. Message and receipt identifiers are excluded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8mu582lbq3kr4tmlpfjb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8mu582lbq3kr4tmlpfjb.png" alt="The backlog-demo message is received and deleted from the queue." width="799" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The selected message body is &lt;code&gt;backlog-demo&lt;/code&gt;, and the deletion confirmation removes it from the queue. Only this fixed test payload is selected for deletion.&lt;/p&gt;

&lt;p&gt;Return to the CloudWatch alarm and wait for two non-breaching one-minute periods to replace the breaching point in the evaluation window. The alarm should return to OK while the queue-depth line is below the threshold.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsofabmoft81g5g1gu2ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsofabmoft81g5g1gu2ck.png" alt="The alarm returns to OK after the queue reports no visible backlog." width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The alarm returns to &lt;code&gt;OK&lt;/code&gt; after the breaching point leaves the two-period window. The graph now reflects the empty visible backlog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The queue state drove both alarm transitions. One queued payload breached Maximum at or above 1, and deleting it produced enough non-breaching data for CloudWatch to recover the alarm.&lt;/p&gt;

&lt;p&gt;For production monitoring, a threshold of one is usually too sensitive. Tune the M-out-of-N window and threshold from observed traffic, and consider pairing backlog depth with the SQS oldest-message-age metric when delay matters more than raw count.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/monitoring-using-cloudwatch.html" rel="noopener noreferrer"&gt;Monitoring Amazon SQS queues using CloudWatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-available-cloudwatch-metrics.html" rel="noopener noreferrer"&gt;Available CloudWatch metrics for Amazon SQS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-access-metrics.html" rel="noopener noreferrer"&gt;Accessing CloudWatch metrics for Amazon SQS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ConsoleAlarms.html" rel="noopener noreferrer"&gt;Create a CloudWatch alarm based on a static threshold&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/alarms-and-missing-data.html" rel="noopener noreferrer"&gt;Configuring how CloudWatch alarms treat missing data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/alarm-evaluation.html" rel="noopener noreferrer"&gt;Alarm evaluation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/cloudwatch/pricing/" rel="noopener noreferrer"&gt;Amazon CloudWatch pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/sqs/pricing/" rel="noopener noreferrer"&gt;Amazon SQS pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>sqs</category>
      <category>cloudwatch</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Send Amazon S3 Object-Creation Events to Amazon SQS</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/send-amazon-s3-object-creation-events-to-amazon-sqs-1el1</link>
      <guid>https://dev.to/miruky/send-amazon-s3-object-creation-events-to-amazon-sqs-1el1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;Amazon S3 can publish object-created event notifications directly to an Amazon SQS Standard queue. The destination queue needs a resource policy that permits the S3 service to call &lt;code&gt;sqs:SendMessage&lt;/code&gt;, and the bucket and queue must be in the same AWS Region.&lt;/p&gt;

&lt;p&gt;This Console run creates one private bucket and one encrypted Standard queue in N. Virginia. After the S3 configuration test message is identified, uploading &lt;code&gt;event-proof.txt&lt;/code&gt; produces an &lt;code&gt;ObjectCreated:Put&lt;/code&gt; record that names only the generated bucket and synthetic object key in the article evidence.&lt;/p&gt;

&lt;p&gt;S3 storage, S3 requests, and SQS requests have separate pricing terms. The validation uses one tiny text object and a few queue operations, but the current pricing pages remain the source of truth for a real workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a private S3 bucket
&lt;/h2&gt;

&lt;p&gt;Both resources in this run use &lt;code&gt;us-east-1&lt;/code&gt; because a direct S3 event-notification queue must be in the same Region as its bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vevnzyg7rkyppyr414w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vevnzyg7rkyppyr414w.png" alt="The English AWS Console shows United States (N. Virginia) before creating either resource." width="799" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header confirms &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the Console is in English. This fixes the shared Region before the bucket and queue are connected.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;S3&lt;/strong&gt;, choose &lt;strong&gt;General purpose buckets&lt;/strong&gt;, and search for &lt;code&gt;miruky-oavzmwjgberlzvhx&lt;/code&gt;. The account-local list should have no exact match; the create request also provides the authoritative shared-global-namespace collision check.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxfi1jhccz3zz9k6z465q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxfi1jhccz3zz9k6z465q.png" alt="The S3 bucket list has no exact account-local match for the generated bucket name." width="799" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The account-local filter for &lt;code&gt;miruky-oavzmwjgberlzvhx&lt;/code&gt; returns no bucket. A successful create request will provide the separate global-name check.&lt;/p&gt;

&lt;p&gt;Create the bucket in the shared global namespace with &lt;code&gt;miruky-oavzmwjgberlzvhx&lt;/code&gt; in N. Virginia. The account regional namespace is outside this article because its name format can expose an account ID.&lt;/p&gt;

&lt;p&gt;The next image vertically stacks two crops from the same create-bucket form. The divider marks the omitted middle controls; it does not join two different runs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8qmtseligihlub9bpl7m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8qmtseligihlub9bpl7m.png" alt="The create-bucket form shows the shared global namespace, generated name, N. Virginia, and Block Public Access enabled." width="800" height="684"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form shows the shared global namespace, &lt;code&gt;miruky-oavzmwjgberlzvhx&lt;/code&gt;, &lt;code&gt;US East (N. Virginia)&lt;/code&gt;, and all Block Public Access settings enabled. ACLs remain disabled and the default encryption remains selected.&lt;/p&gt;

&lt;p&gt;Create the bucket and open its &lt;strong&gt;Properties&lt;/strong&gt; tab. Confirm that the bucket is private and that no event notification exists yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdep3d4oqgn7fcw15y9nb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdep3d4oqgn7fcw15y9nb.png" alt="The new bucket properties show no configured event notifications before the queue is connected." width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bucket's Event notifications area contains no configuration. This is the baseline before a queue destination is added.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create a Standard queue and authorize S3
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Amazon SQS&lt;/strong&gt; and search for &lt;code&gt;miruky-pmannkikxatnzsyf&lt;/code&gt;. The exact-name result should be empty before the queue is created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pajgwhgsscz3obsnj83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pajgwhgsscz3obsnj83.png" alt="The SQS queue list has no exact match for the generated queue name." width="798" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact filter for &lt;code&gt;miruky-pmannkikxatnzsyf&lt;/code&gt; returns no queue. That empty result establishes the queue ownership boundary for this run.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create queue&lt;/strong&gt;, select &lt;strong&gt;Standard&lt;/strong&gt;, and enter &lt;code&gt;miruky-pmannkikxatnzsyf&lt;/code&gt;. Keep the default delivery settings and the default SQS-managed server-side encryption.&lt;/p&gt;

&lt;p&gt;The next image vertically stacks the queue type and encryption portions of the same create-queue form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz1n1hq1m6tas10jqfiuo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz1n1hq1m6tas10jqfiuo.png" alt="The queue creation form shows a Standard queue with the generated name and managed encryption." width="799" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form visibly pairs &lt;code&gt;Standard&lt;/code&gt; with &lt;code&gt;miruky-pmannkikxatnzsyf&lt;/code&gt; and the default SQS-managed encryption. No dead-letter queue or delivery-setting override is added.&lt;/p&gt;

&lt;p&gt;After creation, open the queue's &lt;strong&gt;Access policy&lt;/strong&gt; and choose &lt;strong&gt;Edit&lt;/strong&gt;. Keep the outer policy version at &lt;code&gt;2012-10-17&lt;/code&gt;, then add this statement to the existing owner policy. It does not require you to retrieve or paste an account ID: the queue ARN keeps a wildcard account segment, while &lt;code&gt;aws:SourceAccount&lt;/code&gt; must equal the account that owns the queue at evaluation time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowS3ObjectNotifications"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sqs:SendMessage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:sqs:us-east-1:*:miruky-pmannkikxatnzsyf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ArnEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"aws:SourceArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::miruky-oavzmwjgberlzvhx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"aws:SourceAccount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${aws:ResourceAccount}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The statement is attached to this generated queue and accepts sends only from the exact generated bucket ARN when the bucket's source account equals the queue owner's account. Save the policy, then review the queue overview without exposing its ARN or owner account number. The next image stacks the identity-excluding success and queue-summary portions of that same overview.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2z0t5pd35adtzbljbyvq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2z0t5pd35adtzbljbyvq.png" alt="The queue overview confirms the Standard type and SQS-managed encryption after the policy is saved." width="799" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cropped overview shows &lt;code&gt;Standard&lt;/code&gt; and &lt;code&gt;Amazon SQS key (SSE-SQS)&lt;/code&gt; after the source-restricted policy is saved. ARN and owner fields are excluded from the image.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Create the S3 event notification
&lt;/h2&gt;

&lt;p&gt;Use the following bucket event-notification configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0rbn6m7felffh0jvjaib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0rbn6m7felffh0jvjaib.png" alt="The event-notification form uses the generated name and all object-created event types." width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form shows &lt;code&gt;miruky-vrgqsvmehfuyuoxt&lt;/code&gt; with &lt;code&gt;All object create events&lt;/code&gt; selected. Prefix and suffix filters remain empty.&lt;/p&gt;

&lt;p&gt;For the destination, choose &lt;strong&gt;SQS queue&lt;/strong&gt;, select &lt;code&gt;miruky-pmannkikxatnzsyf&lt;/code&gt;, and save. A successful save means S3 could validate the destination policy and publish its configuration test message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flmoxrst9j6c01e0ssrl0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flmoxrst9j6c01e0ssrl0.png" alt="The saved notification connects all object-created events to the generated Standard queue." width="798" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The saved row connects &lt;code&gt;All object create events&lt;/code&gt; to &lt;code&gt;miruky-pmannkikxatnzsyf&lt;/code&gt;. The next queue poll distinguishes S3's configuration test from an object notification.&lt;/p&gt;

&lt;p&gt;Open the queue, choose &lt;strong&gt;Send and receive messages&lt;/strong&gt;, and poll. The configuration message uses &lt;code&gt;s3:TestEvent&lt;/code&gt;, not the normal &lt;code&gt;Records&lt;/code&gt; array used by object-created notifications, so identify and delete that test message before uploading the proof object.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzm3m4lngdm39igk2fptq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzm3m4lngdm39igk2fptq.png" alt="The queue receives the s3 TestEvent created during notification setup." width="325" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The message body identifies &lt;code&gt;s3:TestEvent&lt;/code&gt;; unlike the later object notification, this configuration message has no normal object-record array. Deleting it leaves the later upload as the only object-event proof.&lt;/p&gt;

&lt;p&gt;S3 documentation notes that notification configuration changes can take about five minutes to become effective. If the test message has arrived but a subsequent object event has not, wait for propagation before changing the configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Upload one object and inspect its event
&lt;/h2&gt;

&lt;p&gt;Return to the S3 bucket, choose &lt;strong&gt;Upload&lt;/strong&gt;, and add the local file &lt;code&gt;event-proof.txt&lt;/code&gt;. The file contains one fixed non-secret line and its object key carries no user or application data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzcmllsnyi0x6l3gvpwzx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzcmllsnyi0x6l3gvpwzx.png" alt="The S3 upload review contains only event-proof.txt in the generated bucket." width="799" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The upload review contains only &lt;code&gt;event-proof.txt&lt;/code&gt; in the generated bucket. Its contents are the fixed non-secret validation line prepared for this run.&lt;/p&gt;

&lt;p&gt;Complete the upload and confirm that the object appears in the bucket. The Console upload uses a &lt;code&gt;PutObject&lt;/code&gt; request for this small file, so the expected event name is &lt;code&gt;ObjectCreated:Put&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzd4jxdcetcramsprpm5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzd4jxdcetcramsprpm5w.png" alt="The bucket object list shows the successfully uploaded event-proof.txt object." width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The object list shows &lt;code&gt;event-proof.txt&lt;/code&gt; with a successful upload status. This completed upload is the action the next queue message must correlate.&lt;/p&gt;

&lt;p&gt;Poll the SQS queue again and open the new message. The complete body stays private because S3 notifications can contain an IAM principal ID, source IP address, request IDs, host ID, ETag, and sequencer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsjc85dglvs5k07fjtiut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsjc85dglvs5k07fjtiut.png" alt="The queue receives a second message after the object upload." width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The isolated receive table now shows &lt;code&gt;Messages (1)&lt;/code&gt;. The separate approximate &lt;strong&gt;Messages available&lt;/strong&gt; counter reads zero because the retrieved message is temporarily in flight; the table count is the relevant proof that one message was returned by this poll. Its full body remains private until sensitive fields are excluded from the evidence crop.&lt;/p&gt;

&lt;p&gt;For comparison, I retained six fields that do not identify the account or operator. The following is a deliberately reduced excerpt transcribed from the verified values, not a copy of the complete message body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws:s3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"awsRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ObjectCreated:Put"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"s3"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"miruky-oavzmwjgberlzvhx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"event-proof.txt"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The screenshot below vertically stacks narrow crops from that same SQS message body. The dividers mark omitted sensitive fields, and the line-wrapped bucket name and object key are joined only at their original wrap points.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F69q38qyr6gq5uddpjg3a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F69q38qyr6gq5uddpjg3a.png" alt="A tightly cropped message view shows only the event name, generated bucket, and object key." width="550" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The retained fields show &lt;code&gt;2.5&lt;/code&gt;, &lt;code&gt;aws:s3&lt;/code&gt;, &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;ObjectCreated:Put&lt;/code&gt;, &lt;code&gt;miruky-oavzmwjgberlzvhx&lt;/code&gt;, and &lt;code&gt;event-proof.txt&lt;/code&gt;. Principal, network, request, host, checksum, and sequencer fields are outside the crop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The queue received two different S3 messages: the configuration-time &lt;code&gt;s3:TestEvent&lt;/code&gt; and the later &lt;code&gt;ObjectCreated:Put&lt;/code&gt; record for the uploaded text file. Separating them prevents the setup test from being mistaken for object-delivery proof.&lt;/p&gt;

&lt;p&gt;S3 Event Notifications are designed for at-least-once delivery and do not guarantee event order. A consumer should tolerate duplicates, use the object key's URL-encoded form correctly, and apply an idempotency strategy appropriate to its workload.&lt;/p&gt;

&lt;p&gt;For a production path, add observability outside this minimal run: send S3 server access logs to a separate logging bucket, enable CloudTrail data events for the source bucket, and monitor relevant S3 and SQS CloudWatch metrics. These controls introduce additional storage, request, or monitoring costs, so include them in the workload estimate.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-event-notifications.html" rel="noopener noreferrer"&gt;Enabling and configuring event notifications using the Amazon S3 console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-event-types-and-destinations.html" rel="noopener noreferrer"&gt;Event notification types and destinations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/grant-destinations-permissions-to-s3.html" rel="noopener noreferrer"&gt;Granting permissions to publish event notification messages to a destination&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html" rel="noopener noreferrer"&gt;IAM Resource element and ARN wildcards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html" rel="noopener noreferrer"&gt;IAM policy variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html" rel="noopener noreferrer"&gt;AWS global condition context keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-enable-disable-notification-intro.html" rel="noopener noreferrer"&gt;Using Amazon SQS, Amazon SNS, and Lambda with S3 Event Notifications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-content-structure.html" rel="noopener noreferrer"&gt;Amazon S3 event message structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html" rel="noopener noreferrer"&gt;Creating a general purpose bucket&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html" rel="noopener noreferrer"&gt;Logging requests with S3 server access logging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/cloudtrail-logging-s3-info.html" rel="noopener noreferrer"&gt;Amazon S3 CloudTrail events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/cloudwatch-monitoring.html" rel="noopener noreferrer"&gt;Monitoring S3 metrics with Amazon CloudWatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-available-cloudwatch-metrics.html" rel="noopener noreferrer"&gt;Available CloudWatch metrics for Amazon SQS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/s3/pricing/" rel="noopener noreferrer"&gt;Amazon S3 pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/sqs/pricing/" rel="noopener noreferrer"&gt;Amazon SQS pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>s3</category>
      <category>sqs</category>
      <category>tutorial</category>
      <category>aws</category>
    </item>
    <item>
      <title>Catch and Recover from a Failed AWS Step Functions Task</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Mon, 31 Aug 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/catch-and-recover-from-a-failed-aws-step-functions-task-2nc1</link>
      <guid>https://dev.to/miruky/catch-and-recover-from-a-failed-aws-step-functions-task-2nc1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;An AWS Step Functions &lt;code&gt;Task&lt;/code&gt; failure ends a workflow unless a retry succeeds or a catcher handles the reported error. A useful catcher should recognize one expected failure without converting unrelated faults into success.&lt;/p&gt;

&lt;p&gt;The design has three fixed parts: an AWS SDK &lt;code&gt;GetParameter&lt;/code&gt; Task, an &lt;code&gt;ErrorEquals&lt;/code&gt; entry for &lt;code&gt;Ssm.ParameterNotFoundException&lt;/code&gt;, and a &lt;code&gt;Recovered&lt;/code&gt; Pass state with a small result object.&lt;/p&gt;

&lt;p&gt;I use an Express workflow with execution logging set to &lt;code&gt;OFF&lt;/code&gt;. The design excludes both Standard workflow history and a CloudWatch Logs copy; Section 4 inspects only the immediate synchronous response.&lt;/p&gt;

&lt;p&gt;No Lambda function, Parameter Store parameter, or CloudWatch Logs log group is created. A clean run of the finished path performs one short Express execution and one &lt;code&gt;GetParameter&lt;/code&gt; request. Check the current Step Functions and Parameter Store pricing pages before adapting the pattern to a recurring workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Establish the missing-parameter condition
&lt;/h2&gt;

&lt;p&gt;The validation starts from an English AWS Console in N. Virginia.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvoi802ydt426laoxz82i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvoi802ydt426laoxz82i.png" alt="The English AWS Console shows the Step Functions service in N. Virginia." width="799" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header shows &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the Step Functions Console is in English. This fixes &lt;code&gt;us-east-1&lt;/code&gt; as the Regional context for the state machine and its Parameter Store call.&lt;/p&gt;

&lt;p&gt;Next, open Parameter Store without creating a parameter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn79ietroo1p4rkg96pe0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn79ietroo1p4rkg96pe0.png" alt="Parameter Store shows its first-use page because the Region contains no parameters." width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The page shows &lt;code&gt;Start to use Parameter Store&lt;/code&gt; and &lt;code&gt;Create parameter&lt;/code&gt;, with no parameter list in this Region. The generated name &lt;code&gt;miruky-awyoqxtnwnrjhexm&lt;/code&gt; is therefore absent, and the exercise never creates it.&lt;/p&gt;

&lt;p&gt;If your account already has parameters, search for the full generated name and confirm that no exact row exists. If it does exist, stop and generate a new name; do not inspect, modify, or delete a resource that belongs to another workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create the execution role
&lt;/h2&gt;

&lt;p&gt;The workflow needs permission to read only the intended parameter path. It does not need permission to create, change, list, or delete parameters.&lt;/p&gt;

&lt;p&gt;The trust policy needs your AWS account ID in two places. Replace both &lt;code&gt;YOUR_ACCOUNT_ID&lt;/code&gt; placeholders in the Console, and keep the real value out of screenshots, notes, and published Markdown.&lt;/p&gt;

&lt;p&gt;Use this trust policy for the role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"states.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ArnLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"aws:SourceArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:states:us-east-1:YOUR_ACCOUNT_ID:stateMachine:*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"aws:SourceAccount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_ACCOUNT_ID"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service principal allows Step Functions to assume the role. The two conditions limit that trust to state machines from the current account in N. Virginia, following the confused-deputy guidance in the Step Functions documentation.&lt;/p&gt;

&lt;p&gt;Add an inline policy named &lt;code&gt;miruky-fswatmkcbokhffcr&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ssm:GetParameter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ssm:us-east-1:*:parameter/miruky-awyoqxtnwnrjhexm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"aws:ResourceAccount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${aws:PrincipalAccount}"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The policy summary makes the permission boundary visible without publishing the account segment of the ARN.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuluqkx964gf6xcn6k0tc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuluqkx964gf6xcn6k0tc.png" alt="The inline policy selects GetParameter as the only Systems Manager action." width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GetParameter&lt;/code&gt; is selected as the only action; the adjacent read operations remain unchecked. No write or list permission broadens the exercise.&lt;/p&gt;

&lt;p&gt;The resource and condition fields provide a separate scope check before the policy is saved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftbtkp4gzowixc47dx3zl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftbtkp4gzowixc47dx3zl.png" alt="The inline policy scopes GetParameter to the generated path and the principal account." width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Resources&lt;/code&gt; field contains one N. Virginia parameter ARN ending in &lt;code&gt;miruky-awyoqxtnwnrjhexm&lt;/code&gt;. The request condition shows &lt;code&gt;aws:ResourceAccount (StringEquals) ${aws:PrincipalAccount}&lt;/code&gt;, so the wildcard account segment cannot authorize a parameter from another account.&lt;/p&gt;

&lt;p&gt;The completed role provides a second checkpoint before Step Functions can assume it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgs4bet3m4j4btq7n2b47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgs4bet3m4j4btq7n2b47.png" alt="The role details show the single generated inline GetParameter policy." width="799" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The role has one permissions policy, the inline policy &lt;code&gt;miruky-fswatmkcbokhffcr&lt;/code&gt;. No unrelated managed policy is attached, while the trust relationship remains the Step Functions policy defined above.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Define the specific catcher
&lt;/h2&gt;

&lt;p&gt;The generated state-machine name must also be unused before creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7a7lct8vzej5jmgp0av1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7a7lct8vzej5jmgp0av1.png" alt="The Step Functions list has no exact match for the generated state-machine name." width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact search for &lt;code&gt;miruky-pxnzifxuhsojmzmb&lt;/code&gt; returns &lt;code&gt;No state machines&lt;/code&gt;. This avoids modifying or replacing an existing workflow.&lt;/p&gt;

&lt;p&gt;Use the following Amazon States Language definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Comment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Catch an expected missing-parameter error."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"StartAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ReadMissingParameter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"States"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ReadMissingParameter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:states:::aws-sdk:ssm:getParameter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"miruky-awyoqxtnwnrjhexm"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Catch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"ErrorEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Ssm.ParameterNotFoundException"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Recovered"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UnexpectedSuccess"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Recovered"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recovered"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"parameter-not-found"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"End"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"UnexpectedSuccess"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UnexpectedParameterPresent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Cause"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The validation parameter existed."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The editor exposes both the intended recovery route and the guard against an unexpected success.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpy6k7bycs717onv2enqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpy6k7bycs717onv2enqf.png" alt="The state-machine editor shows the SDK Task, specific Catch rule, and recovery state." width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ReadMissingParameter&lt;/code&gt; catches only &lt;code&gt;Ssm.ParameterNotFoundException&lt;/code&gt; and routes that value to &lt;code&gt;Recovered&lt;/code&gt;. A successful read goes to &lt;code&gt;UnexpectedSuccess&lt;/code&gt;, which fails the workflow because the precondition would no longer hold.&lt;/p&gt;

&lt;p&gt;The visual workflow exposes the two terminal paths before creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvejjhtr35tlet9b278a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvejjhtr35tlet9b278a1.png" alt="The workflow graph separates the caught recovery branch from the unexpected-success failure branch." width="799" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Catch #1&lt;/code&gt; branch ends at &lt;code&gt;Recovered&lt;/code&gt;, while the normal service-call edge ends at &lt;code&gt;UnexpectedSuccess&lt;/code&gt;. That separation prevents the exercise from reporting success if the supposedly absent parameter is present.&lt;/p&gt;

&lt;p&gt;AWS SDK integrations construct catchable errors differently from the source API name. The Step Functions service prefix is PascalCase, and the exception name needs the &lt;code&gt;Exception&lt;/code&gt; suffix. That is why the SSM API error &lt;code&gt;ParameterNotFound&lt;/code&gt; becomes &lt;code&gt;Ssm.ParameterNotFoundException&lt;/code&gt; in &lt;code&gt;ErrorEquals&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The state-machine configuration controls both execution behavior and retained data. Select the existing role &lt;code&gt;miruky-ijbldxcdcaabrrxe&lt;/code&gt; rather than asking Workflow Studio to create another role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzj4qxgdsscbmv0j9acz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzj4qxgdsscbmv0j9acz7.png" alt="The state-machine settings select the Express workflow type." width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Express&lt;/code&gt; card is selected, which enables a synchronous Console invocation and does not retain Step Functions execution history. The execution role still supplies the narrowly scoped &lt;code&gt;GetParameter&lt;/code&gt; permission at runtime.&lt;/p&gt;

&lt;p&gt;Set the execution log level to &lt;code&gt;OFF&lt;/code&gt; for this one-time validation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx7icfdshhvf8sy2h9jhl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx7icfdshhvf8sy2h9jhl.png" alt="The logging settings show the OFF log level for the Express workflow." width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OFF&lt;/code&gt; prevents Step Functions from delivering execution events to CloudWatch Logs. Because no log destination is configured, this run does not create an execution-history log group.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;Additional configuration&lt;/code&gt; and leave X-Ray tracing disabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5auul1iuf9ia11ku967b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5auul1iuf9ia11ku967b.png" alt="Additional configuration shows Enable X-Ray tracing unchecked." width="800" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Enable X-Ray tracing&lt;/code&gt; checkbox is unchecked, as are the unrelated versioning and customer-managed-key options. The validation therefore adds neither X-Ray traces nor a new KMS dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Run synchronously and verify recovery
&lt;/h2&gt;

&lt;p&gt;A synchronous invocation returns the final output directly to the Console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm4cwk1mu54qe1hkcgqin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm4cwk1mu54qe1hkcgqin.png" alt="The start-execution dialog selects a synchronous run with generated input metadata." width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dialog uses &lt;code&gt;Synchronous&lt;/code&gt;, a generated execution name, and &lt;code&gt;{}&lt;/code&gt; as the input. The input contains no user, account, or application data, and the &lt;code&gt;Open in a new browser tab&lt;/code&gt; checkbox remains unchecked.&lt;/p&gt;

&lt;p&gt;After the short request finishes, the returned result proves which terminal branch ran.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fco0jqq4rql24dmc6qa5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fco0jqq4rql24dmc6qa5w.png" alt="The synchronous Express result succeeds with the deterministic recovery object." width="735" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dialog reports &lt;code&gt;Express execution completed successfully.&lt;/code&gt; and returns an output containing only &lt;code&gt;recovered&lt;/code&gt; and &lt;code&gt;parameter-not-found&lt;/code&gt;. &lt;code&gt;Recovered&lt;/code&gt; is the only state that can produce that object, while an existing parameter would have reached &lt;code&gt;UnexpectedSuccess&lt;/code&gt; and returned a failed result.&lt;/p&gt;

&lt;p&gt;The final Console view confirms that logging-off Express runs do not become stored execution history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzadsesd6clqi88gntea3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzadsesd6clqi88gntea3.png" alt="The Executions tab shows logging disabled and no retained execution rows." width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Executions&lt;/code&gt; tab contains no execution rows and displays &lt;code&gt;Logging is disabled&lt;/code&gt;. This matches the documented Express behavior: Step Functions does not capture Express execution history, and the Console can reconstruct it only from CloudWatch Logs when logging is enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The service call failed for the intended reason, the catcher matched the exact Step Functions error name, and the workflow returned a small success object through &lt;code&gt;Recovered&lt;/code&gt;. A permissions error, malformed request, timeout, or another unexpected task failure would remain unhandled and visible instead of being hidden behind &lt;code&gt;States.ALL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Synchronous Express also kept this one-time validation from leaving a Standard execution record. That choice is useful for this controlled demonstration; production systems may need CloudWatch Logs or a Standard workflow when durable execution history is part of the operational requirement.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-error-handling.html" rel="noopener noreferrer"&gt;Handling errors in Step Functions workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/supported-services-awssdk.html" rel="noopener noreferrer"&gt;Learning to use AWS SDK service integrations in Step Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html" rel="noopener noreferrer"&gt;GetParameter API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/choosing-workflow-type.html" rel="noopener noreferrer"&gt;Choosing workflow type in Step Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/cw-logs.html" rel="noopener noreferrer"&gt;Using CloudWatch Logs to log execution history in Step Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/procedure-create-iam-role.html" rel="noopener noreferrer"&gt;Creating an IAM role for your state machine in Step Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html" rel="noopener noreferrer"&gt;Cross-service confused deputy prevention&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html" rel="noopener noreferrer"&gt;IAM Resource element and ARN wildcards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html" rel="noopener noreferrer"&gt;IAM condition operators for ARN matching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html" rel="noopener noreferrer"&gt;IAM policy variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html" rel="noopener noreferrer"&gt;AWS global condition context keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/step-functions/pricing/" rel="noopener noreferrer"&gt;AWS Step Functions pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/systems-manager/pricing/" rel="noopener noreferrer"&gt;AWS Systems Manager pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>stepfunctions</category>
      <category>serverless</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Reject a Schema-Invalid JSON Body Before API Gateway Calls a Backend</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/reject-a-schema-invalid-json-body-before-api-gateway-calls-a-backend-24jd</link>
      <guid>https://dev.to/miruky/reject-a-schema-invalid-json-body-before-api-gateway-calls-a-backend-24jd</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;A backend should still enforce its own business rules, but it does not need to receive every structurally invalid request. Amazon API Gateway REST APIs can validate a request body against a JSON Schema model before proceeding with the integration request.&lt;/p&gt;

&lt;p&gt;This Console run creates a &lt;code&gt;POST /orders&lt;/code&gt; method with a mock integration. A body missing the required &lt;code&gt;quantity&lt;/code&gt; property returns &lt;code&gt;400&lt;/code&gt; from API Gateway, while a valid body continues through the mock integration and returns &lt;code&gt;200&lt;/code&gt;. No Lambda function, HTTP endpoint, deployment stage, or public invoke URL is required.&lt;/p&gt;

&lt;p&gt;The exercise creates one REST API and uses only Console test invocations. API Gateway request pricing can change and differs by API type and Region, so check the current pricing page before using the pattern for production traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a Regional REST API
&lt;/h2&gt;

&lt;p&gt;API Gateway resources are Regional, so this run keeps the API and its Console tests in &lt;code&gt;us-east-1&lt;/code&gt;. The opening screenshot establishes the English Console and Regional context before any API resources are added.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frwqg8plqyyccfdfh0n6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frwqg8plqyyccfdfh0n6r.png" alt="The English AWS Console shows United States (N. Virginia) before opening API Gateway." width="799" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header confirms &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the API Gateway Console is in English. This fixes the Regional context before any API resources are added.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;API Gateway&lt;/strong&gt;, choose &lt;strong&gt;APIs&lt;/strong&gt;, and search for the exact generated name &lt;code&gt;miruky-zazteywtwjosovgu&lt;/code&gt;. An empty exact-name result prevents this validation from colliding with an existing API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2xehohn63idhktnr60a9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2xehohn63idhktnr60a9.png" alt="The API Gateway list has no exact match for the generated REST API name." width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact filter for &lt;code&gt;miruky-zazteywtwjosovgu&lt;/code&gt; returns no API. That empty result establishes the resource boundary for the validation run.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create API&lt;/strong&gt;, find &lt;strong&gt;REST API&lt;/strong&gt;, and choose &lt;strong&gt;Build&lt;/strong&gt;. Select &lt;strong&gt;New API&lt;/strong&gt;, enter &lt;code&gt;miruky-zazteywtwjosovgu&lt;/code&gt;, choose &lt;strong&gt;Regional&lt;/strong&gt; as the endpoint type, and leave the description empty.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9g2qp405q912nugtjj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9g2qp405q912nugtjj1.png" alt="The REST API create form shows the generated name and Regional endpoint type." width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form visibly pairs &lt;code&gt;miruky-zazteywtwjosovgu&lt;/code&gt; with the &lt;code&gt;Regional&lt;/code&gt; endpoint type. No description or deployment-stage value is added.&lt;/p&gt;

&lt;p&gt;After creation, open &lt;strong&gt;Resources&lt;/strong&gt; and select the root resource. Do not deploy the API; the built-in method test can exercise the configuration without creating a stage or exposing an invoke URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxhbd0hja72vmlduwkmxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxhbd0hja72vmlduwkmxa.png" alt="The new REST API shows only its root resource before the orders method is added." width="670" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The resource tree contains only &lt;code&gt;/&lt;/code&gt; at this point. The next step adds the request path without deploying the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Define the accepted JSON body
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Models&lt;/strong&gt; and choose &lt;strong&gt;Create model&lt;/strong&gt;. Enter the alphanumeric generated name &lt;code&gt;mirukytpnntggdocvcenbn&lt;/code&gt;, use &lt;code&gt;application/json&lt;/code&gt;, and save this JSON Schema draft 4 model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://json-schema.org/draft-04/schema#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7kdce5mx774bopwpehbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7kdce5mx774bopwpehbt.png" alt="The model create form contains the generated name, application/json, and the order schema." width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form shows &lt;code&gt;mirukytpnntggdocvcenbn&lt;/code&gt;, &lt;code&gt;application/json&lt;/code&gt;, and the complete schema together. Those values bind the model to the content type used by both test requests.&lt;/p&gt;

&lt;p&gt;Open the saved model and inspect its schema. The model requires both properties and accepts only an integer of at least 1 for &lt;code&gt;quantity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhzzbwpd4fb54jar2kne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhzzbwpd4fb54jar2kne.png" alt="The saved API Gateway model shows the required fields and quantity constraint." width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The saved schema retains &lt;code&gt;product_id&lt;/code&gt;, &lt;code&gt;quantity&lt;/code&gt;, &lt;code&gt;integer&lt;/code&gt;, and &lt;code&gt;minimum&lt;/code&gt; as configured. That persisted model is the one attached to the method request later.&lt;/p&gt;

&lt;p&gt;API Gateway models for REST APIs use JSON Schema draft 4, with documented feature limitations. Keep application-specific semantic validation in the backend even when the gateway checks the basic payload shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Add a POST method backed by a mock integration
&lt;/h2&gt;

&lt;p&gt;Return to &lt;strong&gt;Resources&lt;/strong&gt;, create the child resource &lt;code&gt;/orders&lt;/code&gt;, and add a &lt;strong&gt;POST&lt;/strong&gt; method. Choose &lt;strong&gt;Mock&lt;/strong&gt; as the integration type so API Gateway itself can provide the successful response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8f1g3omd9egix7xp772y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8f1g3omd9egix7xp772y.png" alt="The POST orders method is created with the Mock integration type." width="620" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The method view visibly combines &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;/orders&lt;/code&gt;, and &lt;code&gt;Mock&lt;/code&gt;. This keeps the accepted path self-contained inside API Gateway for the comparison.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Integration request&lt;/strong&gt;, add an &lt;code&gt;application/json&lt;/code&gt; mapping template with this body. It selects the &lt;code&gt;200&lt;/code&gt; integration response when a request passes method validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvolpxcwiykx18401qsov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvolpxcwiykx18401qsov.png" alt="The mock integration request maps an accepted request to statusCode 200." width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The request template contains &lt;code&gt;{"statusCode": 200}&lt;/code&gt; for &lt;code&gt;application/json&lt;/code&gt;. A request that reaches this integration is therefore routed to the success response.&lt;/p&gt;

&lt;p&gt;On the default &lt;strong&gt;Integration response&lt;/strong&gt;, add an &lt;code&gt;application/json&lt;/code&gt; mapping template that returns a small synthetic response. The response contains no request data and does not echo any header or identifier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"accepted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmhd54gdsv7zubdmdaphw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmhd54gdsv7zubdmdaphw.png" alt="The mock integration response returns a fixed accepted true body." width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The response template contains &lt;code&gt;{"accepted": true}&lt;/code&gt; for &lt;code&gt;application/json&lt;/code&gt;. Its fixed body distinguishes the successful path from a gateway validation error.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;Method request&lt;/strong&gt;, choose &lt;strong&gt;Edit&lt;/strong&gt;, and select &lt;strong&gt;Validate body&lt;/strong&gt; as the request validator. Under &lt;strong&gt;Request body&lt;/strong&gt;, add &lt;code&gt;application/json&lt;/code&gt; and choose &lt;code&gt;mirukytpnntggdocvcenbn&lt;/code&gt; as the model, then save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fba2da8421i5b781hkpkq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fba2da8421i5b781hkpkq.png" alt="The method request validates application/json bodies with the generated model." width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The saved method request shows &lt;code&gt;Validate body&lt;/code&gt;, &lt;code&gt;application/json&lt;/code&gt;, and &lt;code&gt;mirukytpnntggdocvcenbn&lt;/code&gt; together. The validator and model are now attached to the same content type used in the tests.&lt;/p&gt;

&lt;p&gt;The content type matters. API Gateway performs body validation only when the request content type matches a configured model; a production API must define the intended behavior for other content types as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Compare a rejected body with an accepted body
&lt;/h2&gt;

&lt;p&gt;Open the method's &lt;strong&gt;Test&lt;/strong&gt; tab and submit this syntactically valid JSON object. It is structurally invalid for the method because the required &lt;code&gt;quantity&lt;/code&gt; property is absent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sku-001"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F22rs06d8djcpx4l9ygfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F22rs06d8djcpx4l9ygfw.png" alt="The Console test request contains product_id but omits the required quantity property." width="580" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The request body visibly contains &lt;code&gt;product_id&lt;/code&gt; but omits the required quantity property. It is valid JSON, which isolates the model's required-property check from JSON parsing.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Test&lt;/strong&gt; after entering the body, and keep the result pane open for the comparison.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fka562s9ng1ztzkxdnibo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fka562s9ng1ztzkxdnibo.png" alt="The invalid body receives 400 and the test log records request-body validation failure." width="620" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result shows &lt;code&gt;400&lt;/code&gt; and &lt;code&gt;{"message": "Invalid request body"}&lt;/code&gt;. This gateway-generated response confirms that the request stopped at body validation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwk345tzkl5nn3ktzjws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwk345tzkl5nn3ktzjws.png" alt="The execution log identifies the missing required quantity property and completes with status 400." width="620" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The log says &lt;code&gt;Request body does not match model schema for content type application/json&lt;/code&gt;, then identifies &lt;code&gt;quantity&lt;/code&gt; inside the missing-required-properties message. It ends with &lt;code&gt;Method completed with status: 400&lt;/code&gt;, tying the validation failure to the result above.&lt;/p&gt;

&lt;p&gt;Now submit a body that satisfies the same model. The &lt;code&gt;quantity&lt;/code&gt; value is an integer and meets the schema's minimum.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sku-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feq9yd09s6m0g067j8yc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feq9yd09s6m0g067j8yc6.png" alt="The valid Console test request contains both required properties with valid types." width="540" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The valid body visibly contains both &lt;code&gt;product_id&lt;/code&gt; and &lt;code&gt;quantity&lt;/code&gt;, with the latter set to &lt;code&gt;2&lt;/code&gt;. It differs from the rejected body only in the required field under test.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Test&lt;/strong&gt; again without changing the method validator, model, or mock templates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7jxlo8b9h12we0c9nay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7jxlo8b9h12we0c9nay.png" alt="The valid body reaches the mock integration and returns the fixed 200 response." width="620" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The accepted result shows &lt;code&gt;200&lt;/code&gt; and &lt;code&gt;{"accepted": true}&lt;/code&gt;. This fixed body is the response configured on the mock integration path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fks6fngfkvkoe6yg3awnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fks6fngfkvkoe6yg3awnw.png" alt="The valid-request log records successful request validation and the mapped response body." width="620" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The log says &lt;code&gt;Request validation succeeded for content type application/json&lt;/code&gt; and then shows &lt;code&gt;Method response body after transformations&lt;/code&gt;. It ends with &lt;code&gt;Method completed with status: 200&lt;/code&gt;, matching the successful result above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The comparison separates two outcomes at the API boundary. The missing-property body stopped with a gateway-generated HTTP 400, while the model-compliant body proceeded to the integration and received its fixed HTTP 200 response.&lt;/p&gt;

&lt;p&gt;Basic request validation is useful for required fields, types, and supported JSON Schema constraints. Authorization, cross-field business rules, database state, and other application semantics still belong in the appropriate backend controls.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-request-validation.html" rel="noopener noreferrer"&gt;Request validation for REST APIs in API Gateway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-validation-set-up.html" rel="noopener noreferrer"&gt;Set up basic request validation in API Gateway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings-models.html" rel="noopener noreferrer"&gt;Data models for REST APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html" rel="noopener noreferrer"&gt;Amazon API Gateway important notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-mock-integration-console.html" rel="noopener noreferrer"&gt;Enable mock integration using the API Gateway console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-test-method.html" rel="noopener noreferrer"&gt;Use the API Gateway console to test a REST API method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/api-gateway/pricing/" rel="noopener noreferrer"&gt;Amazon API Gateway pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>apigateway</category>
      <category>serverless</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Prevent a Stale DynamoDB Update with a Conditional PartiQL Statement</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Sat, 29 Aug 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/prevent-a-stale-dynamodb-update-with-a-conditional-partiql-statement-4il5</link>
      <guid>https://dev.to/miruky/prevent-a-stale-dynamodb-update-with-a-conditional-partiql-statement-4il5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;Two writers can read the same Amazon DynamoDB item and then try to save different changes. If both writes are unconditional, the later write can overwrite a newer state without realizing that its copy was stale.&lt;/p&gt;

&lt;p&gt;This Console run adds a numeric &lt;code&gt;item_version&lt;/code&gt; attribute and includes the expected version in a PartiQL &lt;code&gt;UPDATE&lt;/code&gt; statement. One update moves the item from version 1 to version 2, a deliberately stale version-1 update fails with &lt;code&gt;ConditionalCheckFailedException&lt;/code&gt;, and a fresh version-2 update succeeds.&lt;/p&gt;

&lt;p&gt;The table uses on-demand capacity and holds one synthetic item. Request and storage charges still follow the current DynamoDB pricing page, so review that page before leaving a table running or adapting the pattern to production traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create an on-demand table and seed version 1
&lt;/h2&gt;

&lt;p&gt;DynamoDB tables and their data are Regional, so every table operation in this run stays in &lt;code&gt;us-east-1&lt;/code&gt;. The first screenshot establishes the English Console and Regional context before any table exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6ybfdou36kwo5fo7qxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6ybfdou36kwo5fo7qxa.png" alt="The English DynamoDB Console shows United States (N. Virginia) on the Tables page." width="799" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header confirms &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the DynamoDB Console is in English. This fixes the Regional context before the table is created.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;DynamoDB&lt;/strong&gt;, choose &lt;strong&gt;Tables&lt;/strong&gt;, and search for the exact generated name &lt;code&gt;miruky-abwmppcmmfrhlnmh&lt;/code&gt;. The result should be empty before the validation creates a table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbejvly0pwgb1tkrtouj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbejvly0pwgb1tkrtouj.png" alt="The DynamoDB table search has no exact match for the generated table name." width="799" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact filter for &lt;code&gt;miruky-abwmppcmmfrhlnmh&lt;/code&gt; reports &lt;code&gt;0 matched results&lt;/code&gt; and &lt;code&gt;No tables found&lt;/code&gt;. That empty result establishes that the validation does not reuse an existing resource.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create table&lt;/strong&gt;. Enter &lt;code&gt;miruky-abwmppcmmfrhlnmh&lt;/code&gt;, set the partition key to &lt;code&gt;item_id&lt;/code&gt; with type &lt;strong&gt;String&lt;/strong&gt;, omit the sort key, and keep the default settings with on-demand capacity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhz7c3o1h543aq60ax6ge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhz7c3o1h543aq60ax6ge.png" alt="The create-table form shows the generated name, String partition key, and default settings." width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form combines &lt;code&gt;miruky-abwmppcmmfrhlnmh&lt;/code&gt;, &lt;code&gt;item_id&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, and the default table settings. No sort key is defined for this singleton-update exercise.&lt;/p&gt;

&lt;p&gt;Wait until the table status is &lt;strong&gt;Active&lt;/strong&gt;. The table needs no secondary index, stream, global-table replica, or sample data beyond the single item used for this comparison.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56veaof5nbyzf5gkwsov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56veaof5nbyzf5gkwsov.png" alt="The table overview shows an Active table with item_id as its only primary-key attribute." width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The overview shows &lt;code&gt;Active&lt;/code&gt; and lists only &lt;code&gt;item_id&lt;/code&gt; in the primary key. The next operation can now seed the single synthetic item.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;PartiQL editor&lt;/strong&gt; and run this singleton &lt;code&gt;INSERT&lt;/code&gt;. The values are deliberately generic and contain no user, account, or application data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="nv"&gt;"miruky-abwmppcmmfrhlnmh"&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s1"&gt;'item_id'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'item-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'item_state'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'item_version'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9dr9awk9n83em0epfxch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9dr9awk9n83em0epfxch.png" alt="The PartiQL editor inserts the synthetic item with draft state and item_version 1." width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The editor reports a successful &lt;code&gt;INSERT&lt;/code&gt; for the controlled item. A read is still needed to prove the stored attribute values.&lt;/p&gt;

&lt;p&gt;Run a key-based &lt;code&gt;SELECT&lt;/code&gt;. The result should contain one item with &lt;code&gt;item_state&lt;/code&gt; set to &lt;code&gt;draft&lt;/code&gt; and &lt;code&gt;item_version&lt;/code&gt; set to &lt;code&gt;1&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"miruky-abwmppcmmfrhlnmh"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'item-001'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjj3s30xny88or672ffm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjj3s30xny88or672ffm.png" alt="A key-based PartiQL SELECT returns the draft item at version 1." width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result shows &lt;code&gt;item_state&lt;/code&gt; as &lt;code&gt;draft&lt;/code&gt; and &lt;code&gt;item_version&lt;/code&gt; as &lt;code&gt;1&lt;/code&gt;. This is the baseline that the first conditional update expects.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Apply a version-checked update
&lt;/h2&gt;

&lt;p&gt;A writer expects version 1, so its condition matches the stored item. It changes the state and increments the version in the same single-item operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="nv"&gt;"miruky-abwmppcmmfrhlnmh"&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;item_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'published'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;item_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'item-001'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;item_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq7o6bujkxyjcbadxmrp9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq7o6bujkxyjcbadxmrp9.png" alt="The conditional UPDATE returns the item with published state and version 2." width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The successful response returns &lt;code&gt;published&lt;/code&gt; and version &lt;code&gt;2&lt;/code&gt;. Those post-update values establish why another writer still expecting version 1 is stale.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RETURNING ALL NEW *&lt;/code&gt; makes the successful post-update attributes visible in the response. The condition still resolves to exactly one primary-key value because the &lt;code&gt;WHERE&lt;/code&gt; clause includes &lt;code&gt;item_id = 'item-001'&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reject a writer holding stale version 1
&lt;/h2&gt;

&lt;p&gt;Now simulate another writer that read the original item but did not see the successful update. It still expects &lt;code&gt;item_version = 1&lt;/code&gt; and attempts to write a different state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="nv"&gt;"miruky-abwmppcmmfrhlnmh"&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;item_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'archived'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;item_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'item-001'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;item_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgmiapfuso4mnzrdczstk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgmiapfuso4mnzrdczstk.png" alt="The stale PartiQL statement still expects version 1 while attempting to write version 3." width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The editor visibly keeps &lt;code&gt;item_version = 1&lt;/code&gt; in the condition while requesting version &lt;code&gt;3&lt;/code&gt;. That mismatch is deliberate; the stored item is already at version 2.&lt;/p&gt;

&lt;p&gt;Run the statement. DynamoDB should return &lt;code&gt;ConditionalCheckFailedException&lt;/code&gt; because the stored item is already at version 2, so the complete &lt;code&gt;WHERE&lt;/code&gt; condition is false.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhw9wp9db6sr0z8yhpp4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhw9wp9db6sr0z8yhpp4v.png" alt="The PartiQL editor reports ConditionalCheckFailedException for the stale version-1 update." width="798" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The editor reports &lt;code&gt;ConditionalCheckFailedException&lt;/code&gt; rather than updated attributes. The rejected response alone does not prove the item stayed unchanged, so the next read checks the stored state.&lt;/p&gt;

&lt;p&gt;The failed condition is the protection mechanism; it is not a partial success. Run the same key-based &lt;code&gt;SELECT&lt;/code&gt; again and confirm that &lt;code&gt;item_state&lt;/code&gt; remains &lt;code&gt;published&lt;/code&gt; and &lt;code&gt;item_version&lt;/code&gt; remains &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fly4q1pef3bf8d5rgkt8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fly4q1pef3bf8d5rgkt8j.png" alt="A follow-up key-based read shows that the rejected stale update changed neither state nor version." width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The follow-up read still shows &lt;code&gt;published&lt;/code&gt; and version &lt;code&gt;2&lt;/code&gt;. Neither protected attribute moved to the stale writer's requested values.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Retry from the current version
&lt;/h2&gt;

&lt;p&gt;A client should not blindly repeat the stale statement. It should read the current item, reconcile its intended change, and submit a new condition using the version it actually observed.&lt;/p&gt;

&lt;p&gt;For this controlled run, update the expectation to version 2 and advance the stored version to 3. The state change is otherwise the same as the rejected attempt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="nv"&gt;"miruky-abwmppcmmfrhlnmh"&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;item_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'archived'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;item_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'item-001'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;item_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjyd7aqzmgwa7qm1zag2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjyd7aqzmgwa7qm1zag2.png" alt="The fresh conditional UPDATE returns archived state and item_version 3." width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fresh update returns &lt;code&gt;archived&lt;/code&gt; and version &lt;code&gt;3&lt;/code&gt; after its condition uses the current version. This contrasts with the rejected statement without changing the intended state transition.&lt;/p&gt;

&lt;p&gt;Run one final key-based &lt;code&gt;SELECT&lt;/code&gt;. The stored item should now contain &lt;code&gt;archived&lt;/code&gt; and version &lt;code&gt;3&lt;/code&gt;, proving that the same logical change succeeds only after its expected version matches the current item.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fag1m2qj6c3n0xz9syjgn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fag1m2qj6c3n0xz9syjgn.png" alt="The final key-based read returns the archived item at version 3." width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final read confirms &lt;code&gt;archived&lt;/code&gt; and version &lt;code&gt;3&lt;/code&gt; as the stored values. The three observed states now match the version-check sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The stale writer did not overwrite the newer DynamoDB item. Its version predicate failed, DynamoDB returned a condition error, and a read showed that both protected attributes remained at their version-2 values.&lt;/p&gt;

&lt;p&gt;In an application, return a conflict to the caller or re-read and reconcile the latest item instead of hiding this failure in an unconditional retry. If several items must change together, use a DynamoDB transaction rather than treating multiple singleton PartiQL statements as one atomic unit.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.update.html" rel="noopener noreferrer"&gt;PartiQL update statements for DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-gettingstarted.html" rel="noopener noreferrer"&gt;Getting started with PartiQL for DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.data-types.html" rel="noopener noreferrer"&gt;PartiQL data types for DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.multiplestatements.transactions.html" rel="noopener noreferrer"&gt;DynamoDB transactions with PartiQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/dynamodb/pricing/" rel="noopener noreferrer"&gt;Amazon DynamoDB pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>dynamodb</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Test an AWS Lambda Function with a Private Console Event</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Fri, 28 Aug 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/test-an-aws-lambda-function-with-a-private-console-event-44ki</link>
      <guid>https://dev.to/miruky/test-an-aws-lambda-function-with-a-private-console-event-44ki</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;A Lambda test event is JSON input that the AWS Console sends directly to a deployed function. It is useful when I need to separate a handler check from an EventBridge rule, API Gateway route, Amazon SQS queue, or another integration.&lt;/p&gt;

&lt;p&gt;This Console run creates a minimal Python function, stores an empty private test event, and reads the fixed response from one synchronous invocation. The handler does not read the event, environment variables, account data, or the invocation context.&lt;/p&gt;

&lt;p&gt;Private test events are available only to their creator. AWS currently allows up to 10 private events per function, and the Console documentation says they require no additional permissions to use. A Console test still counts as a Lambda request and execution duration, and the invocation writes a small record to CloudWatch Logs, so current Lambda and CloudWatch pricing still applies.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a narrow execution role
&lt;/h2&gt;

&lt;p&gt;Lambda functions are Regional, so this exercise keeps the function and its test in &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkffeq6qh9upodsuzccx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkffeq6qh9upodsuzccx2.png" alt="The English AWS Console shows United States (N. Virginia) before the Lambda resources are created." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header confirms the English Console and &lt;code&gt;United States (N. Virginia)&lt;/code&gt; before any Regional resource is created.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;IAM&lt;/strong&gt;, choose &lt;strong&gt;Roles&lt;/strong&gt;, and create a role for the &lt;strong&gt;Lambda&lt;/strong&gt; service. Attach the AWS managed &lt;code&gt;AWSLambdaBasicExecutionRole&lt;/code&gt; policy, then enter the generated role name &lt;code&gt;miruky-vkgewtncrkrrykpz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft5jytl77hs3l7pcpmvs1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft5jytl77hs3l7pcpmvs1.png" alt="The IAM role review shows the generated execution-role name before creation." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The review fixes the generated role name and Lambda use case before creation. &lt;code&gt;AWSLambdaBasicExecutionRole&lt;/code&gt; grants the log-group, log-stream, and log-event permissions needed for basic CloudWatch logging; it does not grant access to application data in another AWS service.&lt;/p&gt;

&lt;p&gt;Open the new role and choose &lt;strong&gt;Trust relationships&lt;/strong&gt;. The trusted service must be &lt;code&gt;lambda.amazonaws.com&lt;/code&gt;, because Lambda assumes this role when it invokes the function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2lil8b48j2cy8n7iw5u0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2lil8b48j2cy8n7iw5u0.png" alt="The role trust policy names lambda.amazonaws.com while the account-scoped ARN remains masked." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The trust policy contains &lt;code&gt;lambda.amazonaws.com&lt;/code&gt;, the Lambda service principal, while the account-scoped ARN is masked. I also checked that the role had one attached managed policy and no inline policy before using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create and deploy the fixed handler
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Lambda&lt;/strong&gt; and search for the exact generated function name &lt;code&gt;miruky-wcyrodwkfbmgaqxd&lt;/code&gt;. The validation account had no function with that name before creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe5625vm17sjntbwvtbzp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe5625vm17sjntbwvtbzp.png" alt="The Lambda function inventory has no match for the generated function name." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The table reports &lt;code&gt;Functions (0)&lt;/code&gt; while the full generated name is in the filter. That empty result establishes the ownership boundary for the function used in this run.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create function&lt;/strong&gt; and &lt;strong&gt;Author from scratch&lt;/strong&gt;. Enter &lt;code&gt;miruky-wcyrodwkfbmgaqxd&lt;/code&gt;, choose &lt;strong&gt;Python 3.14&lt;/strong&gt;, leave the ARM64 switch off for the default &lt;code&gt;x86_64&lt;/code&gt; architecture, and enable &lt;strong&gt;Custom execution role&lt;/strong&gt;. Select &lt;code&gt;miruky-vkgewtncrkrrykpz&lt;/code&gt; as the existing role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqiprpo0wp3ppipil21hu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqiprpo0wp3ppipil21hu.png" alt="The create-function form combines the generated name, Python 3.14, default x86_64 architecture, and generated execution role." width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two retained form sections show the inputs that matter for the exercise. The ARM64 option is off, and its label identifies &lt;code&gt;x86_64&lt;/code&gt; as the default.&lt;/p&gt;

&lt;p&gt;After the function is created, replace the sample code in &lt;code&gt;lambda_function.py&lt;/code&gt; with this handler and choose &lt;strong&gt;Deploy&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Keep the response fixed so the test event cannot change the result.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default handler setting for a Python function created in the Console is &lt;code&gt;lambda_function.lambda_handler&lt;/code&gt;, so the file and function names already match that configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgnhc5j2sh3qbqtea6clu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgnhc5j2sh3qbqtea6clu.png" alt="The Lambda code editor shows the deployed handler and its fixed running response." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The editor contains no credential, account identifier, environment lookup, or request metadata. The deployment confirmation establishes that the later test runs this version rather than an unsaved editor change.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Save an empty private test event
&lt;/h2&gt;

&lt;p&gt;On the function's &lt;strong&gt;Test&lt;/strong&gt; tab, create a new event. Enter the generated event name &lt;code&gt;miruky-debteowwasuirptw&lt;/code&gt;, keep &lt;strong&gt;Private&lt;/strong&gt; selected, and replace the template with an empty JSON object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler ignores its input, so an empty object is enough. Keeping the event private avoids creating a shareable EventBridge schema and avoids exposing the event to other IAM users in the account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frsodqnfw5khgx1q3g5gh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frsodqnfw5khgx1q3g5gh.png" alt="The Test tab contains an empty JSON event while the Shareable option remains unselected." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The event editor shows &lt;code&gt;{}&lt;/code&gt; and leaves &lt;strong&gt;Shareable&lt;/strong&gt; unselected. Choose &lt;strong&gt;Save&lt;/strong&gt; before invoking the function so the same named event can be selected again later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh1yjs6v5yl7pc5nnastn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh1yjs6v5yl7pc5nnastn.png" alt="The Console confirms that the generated private test event was saved." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The success banner contains the generated event name. It confirms that the event was stored before the invocation result was collected.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Invoke the deployed function
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;miruky-debteowwasuirptw&lt;/code&gt; selected, choose &lt;strong&gt;Test&lt;/strong&gt;. The invocation is synchronous, so Lambda waits for the handler response before returning the result or an invocation error.&lt;/p&gt;

&lt;p&gt;Expand &lt;strong&gt;Details&lt;/strong&gt; under the execution result. The expected response is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw93e75uob8jbbeiis6qg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw93e75uob8jbbeiis6qg.png" alt="The synchronous Console test succeeds and returns the fixed running response." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result reports &lt;code&gt;Executing function: succeeded&lt;/code&gt; and returns the same object that the deployed handler contains. Because the event is empty and the response is fixed, this result verifies the deployed runtime, handler name, code, and execution role without adding an external trigger.&lt;/p&gt;

&lt;p&gt;This test does not prove that an API Gateway route, queue, stream, schedule, or resource policy is configured correctly. Those integrations need their own end-to-end test with the real event shape and permissions. The private Console event is the smaller check that I run before introducing those variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The useful evidence is a short chain: an exact-name-empty function inventory, a named execution role with Lambda trust, deployed deterministic code, a saved private &lt;code&gt;{}&lt;/code&gt; event, and a synchronous success response.&lt;/p&gt;

&lt;p&gt;Private events are convenient for repeatable manual checks and account-specific payloads that should not be shared. For a build pipeline or repeated regression suite, move the same assertion into an automated test rather than depending on a person to choose &lt;strong&gt;Test&lt;/strong&gt; in the Console.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/testing-functions.html" rel="noopener noreferrer"&gt;Testing Lambda functions in the console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html" rel="noopener noreferrer"&gt;Building Lambda functions with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html" rel="noopener noreferrer"&gt;Define a Lambda function handler in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html" rel="noopener noreferrer"&gt;Defining Lambda function permissions with an execution role&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/security-iam-awsmanpol.html" rel="noopener noreferrer"&gt;AWS managed policies for AWS Lambda&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;AWS Lambda pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/cloudwatch/pricing/" rel="noopener noreferrer"&gt;Amazon CloudWatch pricing&lt;/a&gt;
``&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Find Who Deleted an AWS Resource with CloudTrail Event History</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Thu, 27 Aug 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/find-who-deleted-an-aws-resource-with-cloudtrail-event-history-342f</link>
      <guid>https://dev.to/miruky/find-who-deleted-an-aws-resource-with-cloudtrail-event-history-342f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;CloudTrail Event history provides a searchable record of recent management activity without requiring a trail. A deletion investigation can start with the API event name, time, Region, resource name, and the AWS principal or session shown in the User name field.&lt;/p&gt;

&lt;p&gt;This controlled Console run creates and deletes one generated SQS queue, then correlates its management event in N. Virginia.&lt;/p&gt;

&lt;p&gt;Event history covers the most recent 90 days of management events in one account and Region, and it accepts one lookup attribute plus a time range. Viewing and searching that history has no additional CloudTrail charge under the current pricing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create one uniquely named queue
&lt;/h2&gt;

&lt;p&gt;Open the English SQS Console in N. Virginia. CloudTrail Event history is Regional, so the test API call and the later lookup both stay in &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feibvb83xp5tctcl46vgx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feibvb83xp5tctcl46vgx.png" alt="The English AWS Console shows United States (N. Virginia) before the test queue is created." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header shows &lt;code&gt;United States (N. Virginia)&lt;/code&gt; while the English SQS Console is open.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;Amazon SQS&lt;/strong&gt; and search for &lt;code&gt;miruky-qnehvscrnwilwtac&lt;/code&gt;. The full-name filter should return no existing queue before the run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F00mhljsgio05ppkrpydt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F00mhljsgio05ppkrpydt.png" alt="The SQS exact-name filter has no match for the generated validation queue." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The search bar reports &lt;code&gt;0 matches&lt;/code&gt;, and the empty results area confirms that the generated queue is not already present.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create queue&lt;/strong&gt;, keep &lt;strong&gt;Standard&lt;/strong&gt; selected, and enter the generated name. Review the queue settings and create it without adding a consumer or resource policy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4e4s4f6a22324ufyljbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4e4s4f6a22324ufyljbt.png" alt="The SQS create form contains the generated name and Standard queue type." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form pairs the exact generated name with the &lt;code&gt;Standard&lt;/code&gt; queue type before creation.&lt;/p&gt;

&lt;p&gt;Open the newly created queue. Its generated name is the correlation key used later in Event history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9nkk8p7dr0emrnmpl2ju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9nkk8p7dr0emrnmpl2ju.png" alt="The created queue details show the generated name without exposing its account-scoped ARN." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The details view confirms the same generated name that will be used for CloudTrail correlation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Produce one deletion management event
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Delete&lt;/strong&gt; and follow the acknowledgement prompt for the generated queue. This action exists here to generate one known control-plane event, not as a general queue-management recommendation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyuwyrbd5n45otpnr5y7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyuwyrbd5n45otpnr5y7j.png" alt="The SQS delete confirmation lists the generated validation queue and the confirm acknowledgement." width="600" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dialog identifies only the owned validation queue, shows that it contains zero messages, and requires the explicit &lt;code&gt;confirm&lt;/code&gt; acknowledgement before deletion.&lt;/p&gt;

&lt;p&gt;After the Console returns to the queue list, filter by the full name again. A zero result establishes that the resource deletion completed before the audit search begins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fusxuh0n2jm7y63h8padz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fusxuh0n2jm7y63h8padz.png" alt="The SQS queue list shows no exact match after the generated queue is deleted." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repeated full-name search is the deletion-completion evidence: it reports &lt;code&gt;0 matches&lt;/code&gt; and returns no queue.&lt;/p&gt;

&lt;p&gt;Record the visible completion time privately. Event history is an audit lookup, not the transactional confirmation of deletion, so use the SQS absence check for completion and refresh CloudTrail separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Narrow Event history to DeleteQueue
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;CloudTrail&lt;/strong&gt;, choose &lt;strong&gt;Event history&lt;/strong&gt;, and keep the Region at N. Virginia. Set a short relative time range that includes the queue deletion.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Event name&lt;/strong&gt; as the lookup attribute and enter &lt;code&gt;DeleteQueue&lt;/code&gt;. Event history allows one attribute filter at a time, so the time range supplies the additional narrowing dimension.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl6tb77i6zblvdlg6v2zo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl6tb77i6zblvdlg6v2zo.png" alt="CloudTrail Event history uses a short time range and the DeleteQueue event-name filter." width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The search is now constrained by &lt;code&gt;Event name&lt;/code&gt; = &lt;code&gt;DeleteQueue&lt;/code&gt; and the visible time range.&lt;/p&gt;

&lt;p&gt;Refresh until the row associated with &lt;code&gt;miruky-qnehvscrnwilwtac&lt;/code&gt; appears. Compare the event time, &lt;code&gt;sqs.amazonaws.com&lt;/code&gt; source, and generated resource name with the queue action rather than selecting an older deletion only because its event name matches.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6lczcznx94w74bdgpixk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6lczcznx94w74bdgpixk.png" alt="The retained Event history crop correlates DeleteQueue with the visible generated queue-name prefix and event time." width="799" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The single row aligns &lt;code&gt;DeleteQueue&lt;/code&gt;, &lt;code&gt;sqs.amazonaws.com&lt;/code&gt;, the event time, and the visible &lt;code&gt;miruky-qnehvscrnwilw...&lt;/code&gt; resource prefix. I hid the User name column before retaining that image. The next detail crop preserves the complete generated name while masking its account-scoped URL segment.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Read the actor field without publishing it
&lt;/h2&gt;

&lt;p&gt;Open the matched event. The cropped summary should preserve the event name, event time, event source, AWS Region, and generated resource reference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs1pwnv87n701pfou88wy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs1pwnv87n701pfou88wy.png" alt="The cropped event summary shows the deletion metadata without identity values or raw JSON." width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The summary preserves the event name, time, source, Region, and generated resource while excluding actor fields and raw JSON.&lt;/p&gt;

&lt;p&gt;On your own screen, read the &lt;strong&gt;User name&lt;/strong&gt; value for the same row or event. CloudTrail documents that this field can identify a user, role name, or service role; it is not always a person's name.&lt;/p&gt;

&lt;p&gt;The lookup menu also exposes &lt;strong&gt;User name&lt;/strong&gt; as a filter. After you have copied the exact value within your protected investigation record, you can switch the single lookup attribute from Event name to User name to find other recent events for that principal or session.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft2bz3estbhmkjmc1pxod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft2bz3estbhmkjmc1pxod.png" alt="The lookup-attribute menu includes User name without displaying the account-specific value." width="274" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The menu confirms that &lt;code&gt;User name&lt;/code&gt; is available as the next lookup attribute without exposing its value.&lt;/p&gt;

&lt;p&gt;If the value represents a federated or assumed-role session, CloudTrail identifies the AWS session, not necessarily the human behind it. Map that session to identity-provider, workforce sign-in, or session-tag records before assigning individual responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The useful result was not &lt;code&gt;DeleteQueue&lt;/code&gt; alone. The generated resource name, Regional event history, event time, service source, and principal or session field together narrowed the deletion to one control-plane request.&lt;/p&gt;

&lt;p&gt;Event history is suitable for a recent, single-account investigation. Retention beyond 90 days, organization-wide search, multiple-field queries, and governed long-term analysis require a trail, CloudTrail Lake, or another centralized audit design.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/view-cloudtrail-events-console.html" rel="noopener noreferrer"&gt;Viewing recent management events with the Console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/view-cloudtrail-events.html" rel="noopener noreferrer"&gt;Working with CloudTrail event history&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-events.html" rel="noopener noreferrer"&gt;Understanding CloudTrail events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/how-cloudtrail-works.html" rel="noopener noreferrer"&gt;How CloudTrail works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/logging-using-cloudtrail.html" rel="noopener noreferrer"&gt;Logging Amazon SQS API calls using CloudTrail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/cloudtrail/pricing/" rel="noopener noreferrer"&gt;AWS CloudTrail pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/sqs/pricing/" rel="noopener noreferrer"&gt;Amazon SQS pricing&lt;/a&gt;
``&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>aws</category>
      <category>cloudtrail</category>
      <category>security</category>
    </item>
    <item>
      <title>Roll Back a Systems Manager Parameter with Version History</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Wed, 26 Aug 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/roll-back-a-systems-manager-parameter-with-version-history-58p5</link>
      <guid>https://dev.to/miruky/roll-back-a-systems-manager-parameter-with-version-history-58p5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;AWS Systems Manager Parameter Store creates a new version each time a parameter value changes. Reapplying an earlier value therefore moves the version number forward; it does not turn the current version back to an old number.&lt;/p&gt;

&lt;p&gt;I tested that behavior with one Standard &lt;code&gt;String&lt;/code&gt; parameter. The value starts as &lt;code&gt;INFO&lt;/code&gt; in version 1, changes to &lt;code&gt;DEBUG&lt;/code&gt; in version 2, and returns to &lt;code&gt;INFO&lt;/code&gt; in version 3. Each state is verified in the AWS Console before the next change.&lt;/p&gt;

&lt;p&gt;The two values are ordinary configuration tokens, not secrets. I did not use &lt;code&gt;SecureString&lt;/code&gt;, a KMS key, an endpoint, a password, or an account-specific value. The current AWS documentation lists Standard parameters at no additional charge; Advanced parameters and higher-throughput interactions have different pricing.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a non-secret Standard parameter
&lt;/h2&gt;

&lt;p&gt;Parameter Store parameters exist only in the Region where they are created. I kept this run in &lt;code&gt;us-east-1&lt;/code&gt; and recorded the Regional context before creating anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd1du0a2i943fly1n26s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd1du0a2i943fly1n26s.png" alt="The English Parameter Store landing page shows United States (N. Virginia) and an empty starting state." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The page still shows &lt;code&gt;United States (N. Virginia)&lt;/code&gt;, and &lt;code&gt;Start to use Parameter Store&lt;/code&gt; confirms that the Region contained no parameters before this run. That empty state also proves that the generated name &lt;code&gt;miruky-isdeziwttyxgqaoc&lt;/code&gt; was not already in use.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create parameter&lt;/strong&gt; and enter the generated name. Keep the tier on &lt;strong&gt;Standard&lt;/strong&gt; and the type on &lt;strong&gt;String&lt;/strong&gt;. Standard is important here because Advanced parameters can incur charges and cannot later be downgraded in place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkgypcka6twtff5g5j7gz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkgypcka6twtff5g5j7gz.png" alt="The create form shows the generated name with Standard and String selected." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The upper form visibly keeps &lt;code&gt;Standard&lt;/code&gt; and &lt;code&gt;String&lt;/code&gt; selected for the generated name. I then moved to the lower part of the same form to enter the value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faguyb0vmfx4bcweucfnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faguyb0vmfx4bcweucfnz.png" alt="The lower part of the create form shows String, text, INFO, and no tags." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The lower form shows &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, and &lt;code&gt;INFO&lt;/code&gt;, while the Tags panel reports no tags. Plain-text parameters must not contain a credential or any other secret. I created the parameter and opened its &lt;strong&gt;Overview&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpbvtexb7e11t8h1yfqqm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpbvtexb7e11t8h1yfqqm.png" alt="The parameter Overview shows version 1 with the non-secret value INFO." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Overview confirms the current value &lt;code&gt;INFO&lt;/code&gt; and version &lt;code&gt;1&lt;/code&gt;. There is only one version at this point, so no rollback behavior has occurred yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create an unwanted version 2
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Edit&lt;/strong&gt; and replace &lt;code&gt;INFO&lt;/code&gt; with &lt;code&gt;DEBUG&lt;/code&gt;. The form keeps the parameter tier and type fixed while allowing the current value to change.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4snjwfndl00lhqcxxixw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4snjwfndl00lhqcxxixw.png" alt="The edit form changes the parameter value from INFO to DEBUG." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Value field now contains &lt;code&gt;DEBUG&lt;/code&gt; while &lt;code&gt;Standard&lt;/code&gt; and &lt;code&gt;String&lt;/code&gt; remain fixed. I saved the change, reopened the parameter, and checked &lt;strong&gt;Overview&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fke0pkgxougc51jv6wcp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fke0pkgxougc51jv6wcp4.png" alt="The parameter Overview shows version 2 with the value DEBUG." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Overview now shows &lt;code&gt;DEBUG&lt;/code&gt; as version &lt;code&gt;2&lt;/code&gt;. This is the deliberately unwanted state, and an unversioned read now resolves to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Confirm that History retained both versions
&lt;/h2&gt;

&lt;p&gt;Open the &lt;strong&gt;History&lt;/strong&gt; tab. The current Console lists versions 2 and 1 as separate rows with their own modification times. The earlier version still exists; the update did not erase it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkbqz8o4dogdb3oq246c1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkbqz8o4dogdb3oq246c1.png" alt="The History tab lists retained versions 2 and 1 as separate rows." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One current-Console detail matters: this History table shows version metadata, not the historical value itself. For this console-only run, I use the &lt;code&gt;INFO&lt;/code&gt; value already recorded on the version-1 Overview page. If the earlier value is unknown, retrieve the required version with a version-aware API, CLI, SDK, or SSM document instead of guessing it.&lt;/p&gt;

&lt;p&gt;History also has no operation that changes the latest pointer back to version 1. Making the known earlier value current requires one more saved edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Restore the value as version 3
&lt;/h2&gt;

&lt;p&gt;I opened the parameter editor and entered the previously recorded value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9znvgurz3zd3suupa4o0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9znvgurz3zd3suupa4o0.png" alt="The edit form restores INFO as the parameter value." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Value field is back to &lt;code&gt;INFO&lt;/code&gt;, while the immutable type remains &lt;code&gt;String&lt;/code&gt;. I saved this change and reopened &lt;strong&gt;Overview&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnb0frskhk6h0e0v0gcxo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnb0frskhk6h0e0v0gcxo.png" alt="The final Overview shows INFO as the current value in version 3." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Overview now shows &lt;code&gt;INFO&lt;/code&gt; with version &lt;code&gt;3&lt;/code&gt;; the rollback is a forward version change. I returned to &lt;strong&gt;History&lt;/strong&gt; once more to check the retained rows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh4n791i6w43yuwxa4wti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh4n791i6w43yuwxa4wti.png" alt="The final History table lists versions 3, 2, and 1." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The table retains &lt;code&gt;3&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, and &lt;code&gt;1&lt;/code&gt; as separate version rows. Version 2 remains part of the audit trail instead of disappearing when &lt;code&gt;INFO&lt;/code&gt; becomes current again.&lt;/p&gt;

&lt;p&gt;An unversioned parameter read now resolves to the latest version, which is version 3. A version-aware client that explicitly requests version 2 remains pinned to the retained version-2 value; restoring the latest value does not rewrite an older version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The rollback in this Console run is an auditable forward change. Version 1 records the original &lt;code&gt;INFO&lt;/code&gt;, version 2 records the temporary &lt;code&gt;DEBUG&lt;/code&gt;, and version 3 makes &lt;code&gt;INFO&lt;/code&gt; current again.&lt;/p&gt;

&lt;p&gt;For production configuration, decide whether consumers follow the latest value, an explicit version, or a movable parameter label. That reference choice determines whether creating a restored current version changes application behavior.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-versions.html" rel="noopener noreferrer"&gt;Working with parameter versions in Parameter Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-create-console.html" rel="noopener noreferrer"&gt;Creating a Parameter Store parameter using the Console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-working-with.html" rel="noopener noreferrer"&gt;Working with Parameter Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html" rel="noopener noreferrer"&gt;AWS Systems Manager Parameter Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-advanced-parameters.html" rel="noopener noreferrer"&gt;Choosing parameter tiers in Parameter Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/systems-manager/pricing/" rel="noopener noreferrer"&gt;AWS Systems Manager pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>systemsmanager</category>
      <category>parameterstore</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Run a One-Time EventBridge Schedule That Deletes Itself</title>
      <dc:creator>miruky</dc:creator>
      <pubDate>Tue, 25 Aug 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/miruky/run-a-one-time-eventbridge-schedule-that-deletes-itself-8ea</link>
      <guid>https://dev.to/miruky/run-a-one-time-eventbridge-schedule-that-deletes-itself-8ea</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hi, I'm miruky.&lt;/p&gt;

&lt;p&gt;An EventBridge Scheduler one-time schedule invokes its target at one date and time. Completion alone does not remove the schedule from the Regional schedule quota, so the create flow offers a separate &lt;strong&gt;Action after schedule completion&lt;/strong&gt; setting.&lt;/p&gt;

&lt;p&gt;This Console run sends &lt;code&gt;one-time-schedule-fired&lt;/code&gt; to a standard SQS queue, then verifies that a schedule configured with &lt;code&gt;DELETE&lt;/code&gt; is absent after the invocation. The schedule uses &lt;code&gt;Asia/Tokyo&lt;/code&gt; as its evaluation time zone while every AWS resource stays in N. Virginia.&lt;/p&gt;

&lt;p&gt;EventBridge Scheduler and SQS meter usage separately. This run makes one scheduled invocation plus a small number of queue requests, but current free-tier eligibility depends on the account and monthly usage; check both pricing pages before repeating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create the SQS target in N. Virginia
&lt;/h2&gt;

&lt;p&gt;This run stays in &lt;code&gt;us-east-1&lt;/code&gt; from resource creation through verification. The schedule and its SQS target must remain in the intended Region throughout the run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffxsivdrdopasdj5sk09t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffxsivdrdopasdj5sk09t.png" alt="The English AWS Console shows United States (N. Virginia) before resource creation." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The header still shows &lt;code&gt;United States (N. Virginia)&lt;/code&gt;, so the Regional context is explicit before the queue is created.&lt;/p&gt;

&lt;p&gt;With N. Virginia still visible in the header, open &lt;strong&gt;Amazon SQS&lt;/strong&gt;, choose &lt;strong&gt;Create queue&lt;/strong&gt;, and keep &lt;strong&gt;Standard&lt;/strong&gt; selected. Enter &lt;code&gt;miruky-fsashpslzjzefiib&lt;/code&gt;, review the encryption and access settings, and create the queue. I kept the default queue settings because the validation needs only one ordinary message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrvfb5oz27f6a0ocntfq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrvfb5oz27f6a0ocntfq.png" alt="The SQS form creates a standard target queue with the generated name." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form visibly keeps &lt;strong&gt;Standard&lt;/strong&gt; selected with the generated queue name.&lt;/p&gt;

&lt;p&gt;The queue is only the target for one &lt;code&gt;SendMessage&lt;/code&gt; call. It does not need a public access policy, an SNS subscription, or a Lambda consumer for this validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Define a one-time schedule
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Amazon EventBridge Scheduler&lt;/strong&gt;, choose &lt;strong&gt;Create schedule&lt;/strong&gt;, and enter &lt;code&gt;miruky-upzizvveloaxuyek&lt;/code&gt;. Keep the existing &lt;code&gt;default&lt;/code&gt; schedule group instead of creating another resource.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;One-time schedule&lt;/strong&gt; and set a date and time at least ten minutes ahead. I selected &lt;code&gt;Asia/Tokyo&lt;/code&gt; as the time zone and turned the delivery window &lt;strong&gt;Off&lt;/strong&gt;, so the review page showed one specific planned time rather than a delivery range.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8a8ojfeihy6kfeirdefm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8a8ojfeihy6kfeirdefm.png" alt="The Scheduler form shows a one-time occurrence, Asia Tokyo, and delivery window Off." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form now binds one date and time to &lt;code&gt;Asia/Tokyo&lt;/code&gt;, and the delivery window reads &lt;code&gt;Off&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The schedule time zone does not move the AWS resources to Tokyo. It only tells Scheduler how to interpret the date and clock time entered in this form; the schedule and queue remain in &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Select the templated SQS target
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Next&lt;/strong&gt;, select &lt;strong&gt;Templated targets&lt;/strong&gt;, and choose &lt;strong&gt;Amazon SQS SendMessage&lt;/strong&gt;. Select only the generated queue &lt;code&gt;miruky-fsashpslzjzefiib&lt;/code&gt; from the target list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6fxs9cjtkwe6msqwacq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6fxs9cjtkwe6msqwacq0.png" alt="The templated target step selects the generated SQS queue without exposing its account-scoped ARN." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The queue selector retains the generated queue name, while the account-scoped portion is masked in the saved image.&lt;/p&gt;

&lt;p&gt;Scheduler accepts text input for this templated SQS target, so the message does not need a JSON wrapper. A fixed payload lets the received body be compared character for character. Enter the following plain-text payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one-time-schedule-fired
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuh1zq6w0sv1anf7okl8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuh1zq6w0sv1anf7okl8t.png" alt="The Scheduler target payload contains the exact one-time test message." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The editor contains the exact value &lt;code&gt;one-time-schedule-fired&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This target type calls &lt;code&gt;sqs:SendMessage&lt;/code&gt;. Scheduler needs an execution role with that permission for the selected queue; selecting a target does not let the service bypass IAM.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Enable automatic deletion and create the role
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;Settings&lt;/strong&gt;, leave the schedule enabled. Under &lt;strong&gt;Action after schedule completion&lt;/strong&gt;, select &lt;strong&gt;DELETE&lt;/strong&gt; rather than &lt;code&gt;NONE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fml9ee40revuzi1ejfy7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fml9ee40revuzi1ejfy7z.png" alt="The Scheduler Settings page shows the schedule enabled and DELETE selected as the action after completion." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The enabled toggle and &lt;code&gt;DELETE&lt;/code&gt; selection are now fixed for this run.&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Create new role for this schedule&lt;/strong&gt; and enter &lt;code&gt;miruky-kkvefpaiwudhkxri&lt;/code&gt; as the role name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjhmzlnzrg2yxvtuctkdo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjhmzlnzrg2yxvtuctkdo.png" alt="The Scheduler permissions panel shows a new execution role with the generated role name." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The permissions panel uses the generated role name while leaving the existing-role option unselected.&lt;/p&gt;

&lt;p&gt;The generated role carries the corresponding target permission for the selected SQS queue.&lt;/p&gt;

&lt;p&gt;I left the optional dead-letter queue unset for this short validation. A production schedule should define failure handling that matches its retry window and the operational path for messages that exhaust retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Review the exact schedule behavior
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Next&lt;/strong&gt; and read the review page from the schedule pattern through the target and settings. Confirm the one-time timestamp, &lt;code&gt;Asia/Tokyo&lt;/code&gt;, delivery window Off, the generated SQS queue, enabled state, and action after completion &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ficbsnh2maividzu6svz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ficbsnh2maividzu6svz3.png" alt="The review page summarizes the one-time schedule pattern and the generated SQS target." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The review repeats the one-time timestamp, time zone, delivery-window setting, and generated queue from the earlier steps. I then created the schedule before the planned time passed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkrhreru8ux7ei4x0apwg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkrhreru8ux7ei4x0apwg.png" alt="The EventBridge Scheduler list shows the generated one-time schedule enabled before execution." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact-name row reports &lt;strong&gt;Enabled&lt;/strong&gt;, so Scheduler has accepted the schedule and is waiting for the one-time invocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Verify the invocation and automatic removal
&lt;/h2&gt;

&lt;p&gt;After the planned time, open &lt;code&gt;miruky-fsashpslzjzefiib&lt;/code&gt;, choose &lt;strong&gt;Send and receive messages&lt;/strong&gt;, and poll for messages. The received body should match the configured target input exactly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo8iw40gkvwf6l2djuos7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo8iw40gkvwf6l2djuos7.png" alt="The SQS receive panel shows one-time-schedule-fired after the scheduled invocation." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The received body exactly matches &lt;code&gt;one-time-schedule-fired&lt;/code&gt;, which confirms the target invocation rather than only the schedule configuration.&lt;/p&gt;

&lt;p&gt;Return to the Scheduler list and filter by the full generated schedule name. After the completed invocation is processed, the exact-name result should reach zero because &lt;code&gt;DELETE&lt;/code&gt; applies after the schedule's last target invocation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0t2zhoxul0v00v4ifjr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0t2zhoxul0v00v4ifjr5.png" alt="The exact-name Scheduler filter shows no schedule after automatic deletion." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The queue message and the absent schedule prove different parts of the run. The message confirms the target call, while the zero-result filter confirms that the completed one-time schedule no longer consumes schedule quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;This one-time schedule used three controls together: a specific evaluation time zone, a templated SQS target, and &lt;code&gt;DELETE&lt;/code&gt; after completion. Removing the schedule automatically did not remove the target queue or the execution role; it affected only the Scheduler resource.&lt;/p&gt;

&lt;p&gt;For longer-lived workloads, add a deliberate retry and dead-letter-queue design, keep the execution role scoped to the target, and monitor failed invocations instead of treating schedule disappearance as the only success signal.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/getting-started.html" rel="noopener noreferrer"&gt;Getting started with EventBridge Scheduler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-schedule-delete.html" rel="noopener noreferrer"&gt;Deleting a schedule in EventBridge Scheduler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html" rel="noopener noreferrer"&gt;Schedule types in EventBridge Scheduler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-templated.html" rel="noopener noreferrer"&gt;Using templated targets in EventBridge Scheduler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/scheduler/latest/UserGuide/setting-up.html" rel="noopener noreferrer"&gt;Setting up EventBridge Scheduler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/general/latest/gr/eventbridgescheduler.html" rel="noopener noreferrer"&gt;EventBridge Scheduler endpoints and quotas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/eventbridge/pricing/" rel="noopener noreferrer"&gt;Amazon EventBridge pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/sqs/pricing/" rel="noopener noreferrer"&gt;Amazon SQS pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>eventbridge</category>
      <category>sqs</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
