diff --git a/examples/powertools-examples-core/kotlin/build.gradle.kts b/examples/powertools-examples-core/kotlin/build.gradle.kts
index 170e5b6d8..94dcbe071 100644
--- a/examples/powertools-examples-core/kotlin/build.gradle.kts
+++ b/examples/powertools-examples-core/kotlin/build.gradle.kts
@@ -14,9 +14,9 @@ dependencies {
     implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2")
     implementation("com.amazonaws:aws-lambda-java-events:3.11.0")
     implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2")
-    aspect("software.amazon.lambda:powertools-tracing:1.18.0")
-    aspect("software.amazon.lambda:powertools-logging:1.18.0")
-    aspect("software.amazon.lambda:powertools-metrics:1.18.0")
+    aspect("software.amazon.lambda:powertools-tracing:1.19.0")
+    aspect("software.amazon.lambda:powertools-logging:1.19.0")
+    aspect("software.amazon.lambda:powertools-metrics:1.19.0")
     testImplementation("junit:junit:4.13.2")
     implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
 }
@@ -36,4 +36,4 @@ tasks.compileTestKotlin {
 // If using JDK 11 or higher, use the following instead:
 //kotlin {
 //    jvmToolchain(11)
-//}
\ No newline at end of file
+//}
diff --git a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java
index 2f020aa25..404137802 100644
--- a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java
+++ b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java
@@ -36,6 +36,7 @@
 import software.amazon.awssdk.http.SdkHttpMethod;
 import software.amazon.awssdk.http.SdkHttpRequest;
 import software.amazon.awssdk.utils.StringInputStream;
+import software.amazon.awssdk.utils.StringUtils;
 
 /**
  * Client for sending responses to AWS CloudFormation custom resources by way of a response URL, which is an Amazon S3
@@ -148,7 +149,9 @@ StringInputStream responseBodyStream(CloudFormationCustomResourceEvent event,
                 ObjectNode node = body.toObjectNode(null);
                 return new StringInputStream(node.toString());
             } else {
-
+                if (!StringUtils.isBlank(resp.getReason())) {
+                    reason = resp.getReason();
+                }
                 String physicalResourceId = resp.getPhysicalResourceId() != null ? resp.getPhysicalResourceId() :
                         event.getPhysicalResourceId() != null ? event.getPhysicalResourceId() :
                                 context.getLogStreamName();
diff --git a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/Response.java b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/Response.java
index fe18000d4..94372ac97 100644
--- a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/Response.java
+++ b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/Response.java
@@ -19,6 +19,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.stream.Collectors;
+import software.amazon.awssdk.utils.StringUtils;
 
 /**
  * Models the arbitrary data to be sent to the custom resource in response to a CloudFormation event. This object
@@ -30,12 +31,22 @@ public class Response {
     private final Status status;
     private final String physicalResourceId;
     private final boolean noEcho;
+    private final String reason;
 
     private Response(JsonNode jsonNode, Status status, String physicalResourceId, boolean noEcho) {
         this.jsonNode = jsonNode;
         this.status = status;
         this.physicalResourceId = physicalResourceId;
         this.noEcho = noEcho;
+        this.reason = null;
+    }
+
+    private Response(JsonNode jsonNode, Status status, String physicalResourceId, boolean noEcho, String reason) {
+        this.jsonNode = jsonNode;
+        this.status = status;
+        this.physicalResourceId = physicalResourceId;
+        this.noEcho = noEcho;
+        this.reason = reason;
     }
 
     /**
@@ -149,6 +160,15 @@ public boolean isNoEcho() {
         return noEcho;
     }
 
+    /**
+     * The reason for the failure.
+     *
+     * @return a potentially null reason
+     */
+    public String getReason() {
+        return reason;
+    }
+
     /**
      * Includes all Response attributes, including its value in JSON format
      *
@@ -161,6 +181,7 @@ public String toString() {
         attributes.put("Status", status);
         attributes.put("PhysicalResourceId", physicalResourceId);
         attributes.put("NoEcho", noEcho);
+        attributes.put("Reason", reason);
         return attributes.entrySet().stream()
                 .map(entry -> entry.getKey() + " = " + entry.getValue())
                 .collect(Collectors.joining(",", "[", "]"));
@@ -182,6 +203,7 @@ public static class Builder {
         private Status status;
         private String physicalResourceId;
         private boolean noEcho;
+        private String reason;
 
         private Builder() {
         }
@@ -263,6 +285,20 @@ public Builder noEcho(boolean noEcho) {
             return this;
         }
 
+        /**
+         * Reason for the response.
+         * Reason is optional for Success responses, but required for Failed responses.
+         * If not provided it will be replaced with cloudwatch log stream name.
+         *
+         * @param reason if null, the default reason will be used
+         * @return a reference to this builder
+         */
+
+        public Builder reason(String reason) {
+            this.reason = reason;
+            return this;
+        }
+
         /**
          * Builds a Response object for the value.
          *
@@ -277,6 +313,9 @@ public Response build() {
                 node = mapper.valueToTree(value);
             }
             Status responseStatus = this.status != null ? this.status : Status.SUCCESS;
+            if (StringUtils.isNotBlank(this.reason)) {
+                return new Response(node, responseStatus, physicalResourceId, noEcho, reason);
+            }
             return new Response(node, responseStatus, physicalResourceId, noEcho);
         }
     }
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java
index 51f0e95f9..938de74d8 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java
@@ -324,4 +324,27 @@ void responseBodyStreamFailedResponse() throws Exception {
                 "}";
         assertThat(stream.getString()).isEqualTo(expectedJson);
     }
+
+    @Test
+    void responseBodyStreamFailedResponseWithReason() throws Exception {
+        CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
+        Context context = mock(Context.class);
+        CloudFormationResponse cfnResponse = testableCloudFormationResponse();
+        String failureReason = "Failed test reason";
+        Response failedResponseWithReason = Response.builder().
+                status(Response.Status.FAILED).reason(failureReason).build();
+        StringInputStream stream = cfnResponse.responseBodyStream(event, context, failedResponseWithReason);
+
+        String expectedJson = "{" +
+                "\"Status\":\"FAILED\"," +
+                "\"Reason\":\"" + failureReason + "\"," +
+                "\"PhysicalResourceId\":null," +
+                "\"StackId\":null," +
+                "\"RequestId\":null," +
+                "\"LogicalResourceId\":null," +
+                "\"NoEcho\":false," +
+                "\"Data\":null" +
+                "}";
+        assertThat(stream.getString()).isEqualTo(expectedJson);
+    }
 }
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java
index 37fe73d0f..42457d918 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java
@@ -33,11 +33,13 @@ void defaultValues() {
         assertThat(response.getStatus()).isEqualTo(Response.Status.SUCCESS);
         assertThat(response.getPhysicalResourceId()).isNull();
         assertThat(response.isNoEcho()).isFalse();
+        assertThat(response.getReason()).isNull();
 
         assertThat(response.toString()).contains("JSON = null");
         assertThat(response.toString()).contains("Status = SUCCESS");
         assertThat(response.toString()).contains("PhysicalResourceId = null");
         assertThat(response.toString()).contains("NoEcho = false");
+        assertThat(response.toString()).contains("Reason = null");
     }
 
     @Test
@@ -61,6 +63,27 @@ void explicitNullValues() {
         assertThat(response.toString()).contains("NoEcho = false");
     }
 
+    @Test
+    void explicitReasonWithDefaultValues() {
+        String reason = "test";
+        Response response = Response.builder()
+                .reason(reason)
+                .build();
+        assertThat(response).isNotNull();
+        assertThat(response.getJsonNode()).isNull();
+        assertThat(response.getStatus()).isEqualTo(Response.Status.SUCCESS);
+        assertThat(response.getPhysicalResourceId()).isNull();
+        assertThat(response.isNoEcho()).isFalse();
+        assertThat(response.getReason()).isNotNull();
+        assertThat(response.getReason()).isEqualTo(reason);
+
+        assertThat(response.toString()).contains("JSON = null");
+        assertThat(response.toString()).contains("Status = SUCCESS");
+        assertThat(response.toString()).contains("PhysicalResourceId = null");
+        assertThat(response.toString()).contains("NoEcho = false");
+        assertThat(response.toString()).contains("Reason = "+reason);
+    }
+
     @Test
     void customNonJsonRelatedValues() {
         Response response = Response.builder()