

# Writing a canary script
<a name="CloudWatch_Synthetics_Canaries_WritingCanary"></a>

The following sections explain how to write a canary script and how to integrate a canary with other AWS services and with external dependencies and libraries.

**Topics**
+ [Writing a canary script using the Java runtime](Synthetics_WritingCanary_Java.md)
+ [Writing a Node.js canary script using the Playwright runtime](Synthetics_WritingCanary_Nodejs_Playwright.md)
+ [Writing a Node.js canary script using the Puppeteer runtime](CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs_Pup.md)
+ [Writing a Python canary script](CloudWatch_Synthetics_Canaries_WritingCanary_Python.md)
+ [Writing a JSON configuration for Node.js multi Checks blueprint](CloudWatch_Synthetics_WritingCanary_Multichecks.md)

# Writing a canary script using the Java runtime
<a name="Synthetics_WritingCanary_Java"></a>

**Topics**
+ [Java project structure for a canary](#Synthetics_canary_Java_package)
+ [Packaging the project for a canary](#Synthetics_canary_Java_package_canary)
+ [Handler name](#Synthetics_canary_Java_handler)
+ [CloudWatch Synthetics configurations](#Synthetics_canary_Java_config)
+ [CloudWatch Synthetics environment variables](#Synthetics_canary_Java_variables)

## Java project structure for a canary
<a name="Synthetics_canary_Java_package"></a>

To create a canary in Java, you need to write your code, compile it, and deploy the compiled artifacts to Synthetics. You can initialize a Java Lambda project in various ways. For instance, you can use a standard Java project setup in your preferred IDE, such as IntelliJ IDEA or Visual Studio Code. Alternatively, you can create the required file structure manually.

A Synthetics Java project contains the following general structure:

```
/project-root
    └ src
        └ main
            └ java
                └ canarypackage // name of package
                |    └ ExampleCanary.java // Canary code file
                |    └ other_supporting_classes
                - resources
                     └ synthetics.json // Synthetics configuration file    
     └ build.gradle OR pom.xml
```

You can use either Maven or Gradle to build your project and manage dependencies.

In the above structure, the `ExampleCanary` class is the entry point or handler for the canary.

 **Java canary class example** 

This example is for a canary to make a get request to a URL stored in the * TESTING\$1URL* Lambda environment variable. The canary does not use any of the methods provided by Synthetics runtime.

```
package canarypackage;

import java.net.HttpURLConnection;
import java.net.URL;

// Handler value: canary.ExampleCanary::canaryCode
public class ExampleCanary { 
  public void canaryCode() throws Exception{ 
      URL url = new URL(System.getenv("TESTING_URL"));
      HttpURLConnection con=(HttpURLConnection)url.openConnection();
      con.setRequestMethod("GET");
      con.setConnectTimeout(5000);
      con.setReadTimeout(5000);
      int status=con.getResponseCode();
      if(status!=200){
        throw new Exception("Failed to load " + url + ", status code: " + status);
      }
  }
}
```

It is highly recommended to modularize your canaries using the Synthetics provided library function `executeStep`. The canary makes `get` calls to two separate URLs procured from URL1 and URL2 environment variables.

**Note**  
To use the `executeStep` functionality, the handler method for the canary should take in a parameter of type Synthetics as shown below. 

```
package canarypackage;

import com.amazonaws.synthetics.Synthetics;
import java.net.HttpURLConnection;
import java.net.URL;

// Handler value: canary.ExampleCanary::canaryCode
public class ExampleCanary {
  public void canaryCode(Synthetics synthetics) throws Exception {
    createStep("Step1", synthetics, System.getenv("URL1"));
    createStep("Step2", synthetics, System.getenv("URL2"));
    return;
  }
  
  private void createStep(String stepName, Synthetics synthetics, String url) throws Exception{
    synthetics.executeStep(stepName,()->{
      URL obj=new URL(url);
      HttpURLConnection con=(HttpURLConnection)obj.openConnection();
      con.setRequestMethod("GET");
      con.setConnectTimeout(5000);
      con.setReadTimeout(5000);
      int status=con.getResponseCode();
      if(status!=200){
        throw new Exception("Failed to load" + url + "status code:" + status);
      }
      return null;
    }).get();
  }
}
```

## Packaging the project for a canary
<a name="Synthetics_canary_Java_package_canary"></a>

Synthetics accepts code for a java canary in the *zip* format. The zip consists of the class files for the canary code, the jars for any third party dependencies and the Synthetics configuration file.

A Synthetics Java zip contains the following general structure.

```
example-canary
    └ lib
    |  └ //third party dependency jars
       └ java-canary.jar
    └ synthetics.json
```

To build this zip from the above project structure, you can use gradle (build.gradle) or maven (pom.xml). Here is an example.

For information on compile time dependencies or interfaces for the Synthetics library, see the README under [ aws-cloudwatch-synthetics-sdk-java ](https://github.com/aws/aws-cloudwatch-synthetics-sdk-java/tree/main).

```
plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

dependencies {
    // Third party dependencies 
    // example: implementation 'software.amazon.awssdk:s3:2.31.9'
    
    // Declares dependency on Synthetics interfaces for compiling only
    // Refer https://github.com/aws/aws-cloudwatch-synthetics-sdk-java for building from source.
    compileOnly 'software.amazon.synthetics:aws-cloudwatch-synthetics-sdk-java:1.0.0'}

test {
    useJUnitPlatform()
}

// Build the zip to be used as Canary code.
task buildZip(type: Zip) {

    archiveFileName.set("example-canary.zip")
    destinationDirectory.set(file("$buildDir"))
    
    from processResources
    into('lib') {
        from configurations.runtimeClasspath
        from(tasks.named("jar"))
    }
    from "src/main/java/resources/synthetics.json"
    
    doLast {
        println "Artifact written to: ${archiveFile.get().asFile.absolutePath}"
    }
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

tasks.named("build") {
    dependsOn "buildZip"
}
```

## Handler name
<a name="Synthetics_canary_Java_handler"></a>

The handler name is the entry point for the canary. For Java runtime, the handler is in the following format.

```
<<full qualified name for canary class>>::<<name of the method to start the execution from>>
// for above code: canarypackage.ExampleCanary::canaryCode
```

## CloudWatch Synthetics configurations
<a name="Synthetics_canary_Java_config"></a>

You can configure the behavior of the Synthetics Java runtime by providing an optional JSON configuration file named `synthetics.json`. This file should be packaged in the root directory of the package zip. Though a configuration file is optional, if you don't provide a configuration file, or a configuration key is missing, CloudWatch uses the defaults.

The following are supported configuration values, and their defaults.

```
{
    "step": {
        "stepSuccessMetric": true,
        "stepDurationMetric": true,
        "continueOnStepFailure": false,
        "stepsReport": true
    },
    "logging": {
        "logRequest": false,
        "logResponse": false
    },
    "httpMetrics": {
        "metric_2xx": true,
        "metric_4xx": true,
        "metric_5xx": true,
        "aggregated2xxMetric": true,
        "aggregated4xxMetric": true,
        "aggregated5xxMetric": true
    },
    "canaryMetrics": {
        "failedCanaryMetric": true,
        "aggregatedFailedCanaryMetric": true
    }
}
```

 **Step configurations** 
+ *continueOnStepFailure* – Determines if a script should continue even after a step has failed. The default is false.
+ *stepSuccessMetric* – Determines if a step's ` SuccessPercent` metric is emitted. The `SuccessPercent` metric for a step is *100* for the canary run if the step succeeds, and * 0 * if the step fails. The default is *true*.
+ *stepDurationMetric* – Determines if a step's * Duration* metric is emitted. The *Duration* metric is emitted as a duration, in milliseconds, of the step's run. The default is * true*.

 **Logging configurations** 

Applies to logs generated by CloudWatch Synthetics. Controls the verbosity of request and response logs.
+ *logRequest* – Specifies whether to log every request in canary logs. The default is false.
+ *logResponse* – Specifies whether to log every response in canary logs. The default is false.

 **HTTP metric configurations** 

Configurations for metrics related to the count of network requests with different HTTP status codes, emitted by CloudWatch Synthetics for this canary.
+ *metric\$12xx* – Specifies whether to emit the * 2xx* metric (with the CanaryName dimension) for this canary. The default is *true*.
+ *metric\$14xx* – Specifies whether to emit the * 4xx* metric (with the CanaryName dimension) for this canary. The default is *true*.
+ *metric\$15xx* – Specifies whether to emit the * 5xx* metric (with the CanaryName dimension) for this canary. The default is *true*.
+ *aggregated2xxMetric* – Specifies whether to emit the * 2xx* metric (without the CanaryName dimension) for this canary. The default is *true*.
+ *aggregated4xxMetric* – Specifies whether to emit the * 4xx* metric (without the CanaryName dimension) for this canary. The default is *true*.
+ *aggregated5xxMetric* – Specifies whether to emit the * 5xx* metric (without the CanaryName dimension) for this canary. The default is *true*.

 **Canary metric configurations** 

Configurations for other metrics emitted by CloudWatch Synthetics.
+ *failedCanaryMetric* Network Access Analyzer Specifies whether to emit the * Failed* metric (with the CanaryName dimension) for this canary. The default is *true*.
+ *aggregatedFailedCanaryMetric* – Specifies whether to emit the *Failed* metric (without the CanaryName dimension) for this canary. The default is *true*.

## CloudWatch Synthetics environment variables
<a name="Synthetics_canary_Java_variables"></a>

You can configure the logging level and format by using environment variables.

 **Log format** 

The CloudWatch Synthetics Java runtime creates CloudWatch logs for every canary run. Logs are written in JSON format for convenient querying. Optionally, you can change the log format to *TEXT*.
+ *Environment variable name* – CW\$1SYNTHETICS\$1LOG\$1FORMAT
+ *Supported values* – JSON, TEXT
+ *Default* –JSON

 **Log levels** 
+ *Environment variable name* – CW\$1SYNTHETICS\$1LOG\$1LEVEL
+ *Supported values* – TRACE, DEBUG, INFO, WARN, ERROR, FATAL
+ *Default* – INFO

Apart from the above environment variables, there is a default environment variable added for Java runtime, `AWS_LAMBDA-EXEC_WRAPPER` environment variable to your function, and set its value to `/opt/synthetics-otel-instrument`. This environment variable modifies your function's startup behavior for telemetry. If this environment variable already exists, make sure that it's set to the required value.

# Writing a Node.js canary script using the Playwright runtime
<a name="Synthetics_WritingCanary_Nodejs_Playwright"></a>

**Topics**
+ [Packaging your Node.js canary files for the Playwright runtime](#Synthetics_canary_Nodejs_Playwright_package)
+ [Changing an existing Playwright script to use as a CloudWatch Synthetics canary](#CloudWatch_Synthetics_canary_edit_Playwright_script)
+ [CloudWatch Synthetics configurations](#Synthetics_canary_configure_Playwright_script)

## Packaging your Node.js canary files for the Playwright runtime
<a name="Synthetics_canary_Nodejs_Playwright_package"></a>

 Your canary script comprises of a `.js` (CommonJS syntax) or `.mjs` (ES syntax) file containing your Synthetics handler code, together with any additional packages and modules your code depends on. Scripts created in ES (ECMAScript) format should either use .mjs as the extension or include a package.json file with the "type": "module" field set. Unlike other runtimes like Node.js Puppeteer, you are not required to save your scripts in a specific folder structure. You can package your scripts directly. Use your preferred `zip` utility to create a `.zip` file with your handler file at the root. If your canary script depends on additional packages or modules that aren't included in the Synthetics runtime, you can add these dependencies to your `.zip` file. To do so, you can install your function's required libraries in the `node_modules` directory by running the `npm install` command. The following example CLI commands create a `.zip` file named `my_deployment_package.zip` containing the `index.js` or `index.mjs` file (Synthetics handler) and its dependencies. In the example, you install dependencies using the `npm` package manager.

```
~/my_function
├── index.mjs
├── synthetics.json
├── myhelper-util.mjs    
└── node_modules
    ├── mydependency
```

Create a `.zip` file that contains the contents of your project folder at the root. Use the `r` (recursive) option, as shown in the following example, to ensure that `zip` compresses the subfolders.

```
zip -r my_deployment_package.zip .
```

Add a Synthetics configuration file to configure the behavior of CloudWatch Synthetics. You can create a `synthetics.json` file and save it at the same path as your entry point or handler file.

Optionally, you can also store your entry point file in a folder structure of your choice. However, be sure that the folder path is specified in your handler name.

 **Handler name** 

Be sure to set your canary’s script entry point (handler) as ` myCanaryFilename.functionName` to match the file name of your script’s entry point. You can optionally store the canary in a separate folder such as ` myFolder/my_canary_filename.mjs`. If you store it in a separate folder, specify that path in your script entry point, such as ` myFolder/my_canary_filename.functionName`.

## Changing an existing Playwright script to use as a CloudWatch Synthetics canary
<a name="CloudWatch_Synthetics_canary_edit_Playwright_script"></a>

You can edit an existing script for Node.js and Playwright to be used as a canary. For more information about Playwright, see the [Playwright library](https://playwright.dev/docs/api/class-playwright) documentation. 

You can use the following Playwright script that is saved in file ` exampleCanary.mjs`.

```
import { chromium } from 'playwright';
import { expect } from '@playwright/test';

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com', {timeout: 30000});
await page.screenshot({path: 'example-home.png'});

const title = await page.title();
expect(title).toEqual("Example Domain");
 
await browser.close();
```

Convert the script by performing the following steps:

1. Create and export a `handler` function. The handler is the entry point function for the script. You can choose any name for the handler function, but the function that is used in your script should be the same as in your canary handler. If your script name is `exampleCanary.mjs`, and the handler function name is `myhandler`, your canary handler is named `exampleCanary.myhandler`. In the following example, the handler function name is `handler`.

   ```
   exports.handler = async () => {
     // Your script here
     };
   ```

1. Import the `Synthetics Playwright module` as a dependency.

   ```
   import { synthetics } from '@aws/synthetics-playwright';
   ```

1. Launch a browser using the Synthetics `Launch` function.

   ```
   const browser = await synthetics.launch();
   ```

1. Create a new Playwright page by using the Synthetics `newPage` function.

   ```
   const page = await synthetics.newPage();
   ```

Your script is now ready to be run as a Synthetics canary. The following is the the updated script:

 **Updated script in ES6 format** 

The script file saved with a `.mjs` extension.

```
import { synthetics } from '@aws/synthetics-playwright';
import { expect } from '@playwright/test';

export const handler = async (event, context) => {
  try {
        // Launch a browser
        const browser = await synthetics.launch();
        
        // Create a new page
        const page = await synthetics.newPage(browser);
        
        // Navigate to a website
        await page.goto('https://www.example.com', {timeout: 30000});
        
        // Take screenshot
        await page.screenshot({ path: '/tmp/example.png' });
        
        // Verify the page title
        const title = await page.title();
        expect(title).toEqual("Example Domain");
    } finally {
        // Ensure browser is closed
        await synthetics.close();
    }
};
```

 **Updated script in CommonJS format** 

The script file saved with a `.js` extension.

```
const { synthetics } = require('@aws/synthetics-playwright');
const { expect } = require('@playwright/test');

exports.handler = async (event) => {
  try {
    const browser = await synthetics.launch();
    const page = await synthetics.newPage(browser);
    await page.goto('https://www.example.com', {timeout: 30000});
    await page.screenshot({ path: '/tmp/example.png' });
    const title = await page.title();
    expect(title).toEqual("Example Domain");
  } finally {
    await synthetics.close();
  }
};
```

## CloudWatch Synthetics configurations
<a name="Synthetics_canary_configure_Playwright_script"></a>

You can configure the behavior of the Synthetics Playwright runtime by providing an optional JSON configuration file named `synthetics.json`. This file should be packaged in the same location as the handler file. Though a configuration file is optional, f you don't provide a configuration file, or a configuration key is missing, CloudWatch assumes defaults.

 **Packaging your configuration file** 

The following are supported configuration values, and their defaults.

```
{
    "step": {
        "screenshotOnStepStart": false,
        "screenshotOnStepSuccess": false,
        "screenshotOnStepFailure": false,
        "stepSuccessMetric": true,
        "stepDurationMetric": true,
        "continueOnStepFailure": true,
        "stepsReport": true
    },
    "report": {
        "includeRequestHeaders": true,
        "includeResponseHeaders": true,
        "includeUrlPassword": false,
        "includeRequestBody": true,
        "includeResponseBody": true,
        "restrictedHeaders": ['x-amz-security-token', 'Authorization'], // Value of these headers is redacted from logs and reports
        "restrictedUrlParameters": ['Session', 'SigninToken'] // Values of these url parameters are redacted from logs and reports
    },
    "logging": {
        "logRequest": false,
        "logResponse": false,
        "logResponseBody": false,
        "logRequestBody": false,
        "logRequestHeaders": false,
        "logResponseHeaders": false
    },
    "httpMetrics": {
        "metric_2xx": true,
        "metric_4xx": true,
        "metric_5xx": true,
        "failedRequestsMetric": true,
        "aggregatedFailedRequestsMetric": true,
        "aggregated2xxMetric": true,
        "aggregated4xxMetric": true,
        "aggregated5xxMetric": true
    },
    "canaryMetrics": {
        "failedCanaryMetric": true,
        "aggregatedFailedCanaryMetric": true
    },
    "userAgent": "",
    "har": true
}
```

 **Step configurations** 
+ `screenshotOnStepStart` – Determines if Synthetics should capture a screenshot before the step starts. The default is `true`. 
+ `screenshotOnStepSuccess` – Determines if Synthetics should capture a screenshot after a step has succeeded. The default is `true`. 
+ `screenshotOnStepFailure` – Determines if Synthetics should capture a screenshot after a step has failed. The default is `true`. 
+ `continueOnStepFailure` – Determines if a script should continue even after a step has failed. The default is `false`. 
+ `stepSuccessMetric` – Determines if a step’s ` SuccessPercent` metric is emitted. The `SuccessPercent` metric for a step is `100` for the canary run if the step succeeds, and `0` if the step fails. The default is `true`. 
+ `stepDurationMetric` – Determines if a step's `Duration` metric is emitted. The `Duration` metric is emitted as a duration, in milliseconds, of the step's run. The default is `true`.

 **Report configurations** 

Includes all reports generated by CloudWatch Synthetics, such as a HAR file and a Synthetics steps report. Sensitive data redaction fields `restrictedHeaders` and `restrictedUrlParameters` also apply to logs generated by Synthetics. 
+ `includeRequestHeaders` – Whether to include request headers in the report. The default is `false`. 
+ `includeResponseHeaders` – Whether to include response headers in the report. The default is `false`.
+ `includeUrlPassword` – Whether to include a password that appears in the URL. By default, passwords that appear in URLs are redacted from logs and reports, to prevent the disclosure of sensitive data. The default is `false` . 
+ `includeRequestBody` – Whether to include the request body in the report. The default is `false`. 
+ `includeResponseBody` – Whether to include the response body in the report. The default is `false`. 
+ `restrictedHeaders` – A list of header values to ignore, if headers are included. This applies to both request and response headers. For example, you can hide your credentials by passing `includeRequestHeaders` as true and `restrictedHeaders` as `['Authorization']`. 
+ `restrictedUrlParameters` – A list of URL path or query parameters to redact. This applies to URLs that appear in logs, reports, and errors. The parameter is case-insensitive. You can pass an asterisk (`*`) as a value to redact all URL path and query parameter values. The default is an empty array. 
+ `har` – Determines if an HTTP archive (HAR) should be generated. The default is `true`.

The following is an example of a report configurations file.

```
"includeRequestHeaders": true,
"includeResponseHeaders": true,
"includeUrlPassword": false,
"includeRequestBody": true,
"includeResponseBody": true,
"restrictedHeaders": ['x-amz-security-token', 'Authorization'], // Value of these headers is redacted from logs and reports
"restrictedUrlParameters": ['Session', 'SigninToken'] // Values of these URL parameters are redacted from logs and reports
```

 **Logging configurations** 

Applies to logs generated by CloudWatch Synthetics. Controls the verbosity of request and response logs.
+ `logRequest` – Whether to log every request in canary logs. For UI canaries, this logs each request sent by the browser. The default is ` false`. 
+ `logResponse` – Whether to log every response in canary logs. For UI canaries, this logs every response received by the browser. The default is ` false`. 
+ `logRequestBody` – Whether to log request bodies along with the requests in canary logs. This configuration applies only if `logRequest` is true. The default is `false`. 
+ `logResponseBody` – Whether to log response bodies along with the requests in canary logs. This configuration applies only if `logResponse` is true. The default is `false`. 
+ `logRequestHeaders` – Whether to log request headers along with the requests in canary logs. This configuration applies only if ` logRequest` is true. The default is `false`. 
+ `logResponseHeaders` – Whether to log response headers along with the responses in canary logs. This configuration applies only if ` logResponse` is true. The default is `false`. 

 **HTTP metric configurations** 

Configurations for metrics related to the count of network requests with different HTTP status codes, emitted by CloudWatch Synthetics for this canary.
+ `metric_2xx` – Whether to emit the `2xx` metric (with the `CanaryName` dimension) for this canary. The default is ` true`. 
+ `metric_4xx` – Whether to emit the `4xx` metric (with the `CanaryName` dimension) for this canary. The default is ` true`. 
+ `metric_5xx` – Whether to emit the `5xx` metric (with the `CanaryName` dimension) for this canary. The default is ` true`. 
+ `failedRequestsMetric` – Whether to emit the ` failedRequests` metric (with the `CanaryName` dimension) for this canary. The default is `true`. 
+ `aggregatedFailedRequestsMetric` – Whether to emit the ` failedRequests` metric (without the `CanaryName` dimension) for this canary. The default is `true`. 
+ `aggregated2xxMetric` – Whether to emit the `2xx` metric (without the `CanaryName` dimension) for this canary. The default is `true`. 
+ `aggregated4xxMetric` – Whether to emit the `4xx` metric (without the `CanaryName` dimension) for this canary. The default is `true`. 
+ `aggregated5xxMetric` – Whether to emit the `5xx` metric (without the `CanaryName` dimension) for this canary. The default is `true`. 

 **Canary metric configurations** 

Configurations for other metrics emitted by CloudWatch Synthetics.
+ `failedCanaryMetric` – Whether to emit the `Failed` metric (with the `CanaryName` dimension) for this canary. The default is ` true`. 
+ `aggregatedFailedCanaryMetric` – Whether to emit the ` Failed` metric (without the `CanaryName` dimension) for this canary. The default is `true`. 

 **Other configurations** 
+ `userAgent` – A string to append to the user agent. The user agent is a string that is included in request header, and identifies your browser to websites you visit when you use the headless browser. CloudWatch Synthetics automatically adds `CloudWatchSynthetics/canary-arn to the user agent`. The specified configuration is appended to the generated user agent. The default user agent value to append is an empty string (`""`).

### CloudWatch Synthetics environment variables
<a name="Synthetics_canary_Nodejs_Playwright_script"></a>

Configure the logging level and format by using environment variables.

 **Log format** 

The CloudWatch Synthetics Playwright runtime creates CloudWatch logs for every canary run. Logs are written in JSON format for convenient querying. Optionally, you can change the log format to `TEXT`.
+ `Environment variable name` – CW\$1SYNTHETICS\$1LOG\$1FORMAT 
+ `Supported values` – JSON, TEXT 
+ `Default` – JSON 

 **Log levels** 

Though enabling `Debug` mode increases verbosity, it can be useful for troubleshooting.
+ `Environment variable name` – CW\$1SYNTHETICS\$1LOG\$1LEVEL
+ `Supported values` – TRACE, DEBUG, INFO, WARN, ERROR, FATAL 
+ `Default` – INFO

# Writing a Node.js canary script using the Puppeteer runtime
<a name="CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs_Pup"></a>

**Topics**
+ [Creating a CloudWatch Synthetics canary from scratch](#CloudWatch_Synthetics_Canaries_write_from_scratch)
+ [Packaging your Node.js canary files](#CloudWatch_Synthetics_Canaries_package)
+ [Changing an existing Puppeteer script to use as a Synthetics canary](#CloudWatch_Synthetics_Canaries_modify_puppeteer_script)
+ [Environment variables](#CloudWatch_Synthetics_Environment_Variables)
+ [Integrating your canary with other AWS services](#CloudWatch_Synthetics_Canaries_AWS_integrate)
+ [Forcing your canary to use a static IP address](#CloudWatch_Synthetics_Canaries_staticIP)

## Creating a CloudWatch Synthetics canary from scratch
<a name="CloudWatch_Synthetics_Canaries_write_from_scratch"></a>

Here is an example minimal Synthetics Canary script. This script passes as a successful run, and returns a string. To see what a failing canary looks like, change `let fail = false;` to `let fail = true;`. 

You must define an entry point function for the canary script. To see how files are uploaded to the Amazon S3 location specified as the canary's `ArtifactS3Location`, create these files in the `/tmp` folder. All canary artifacts should be stored in `/tmp`, because it's the only writable directory. Be sure that the screenshot path is set to `/tmp` for any screenshots or other files created by the script. Synthetics automatically uploads files in ` /tmp` to an S3 bucket.

```
/tmp/<name>
```

After the script runs, the pass/fail status and the duration metrics are published to CloudWatch and the files under`/tmp` are uploaded to an S3 bucket.

```
const basicCustomEntryPoint = async function () {

    // Insert your code here

    // Perform multi-step pass/fail check

    // Log decisions made and results to /tmp

    // Be sure to wait for all your code paths to complete 
    // before returning control back to Synthetics.
    // In that way, your canary will not finish and report success
    // before your code has finished executing

    // Throw to fail, return to succeed
    let fail = false;
    if (fail) {
        throw "Failed basicCanary check.";
    }

    return "Successfully completed basicCanary checks.";
};

exports.handler = async () => {
    return await basicCustomEntryPoint();
};
```

Next, we'll expand the script to use Synthetics logging and make a call using the AWS SDK. For demonstration purposes, this script will create an Amazon DynamoDB client and make a call to the DynamoDB listTables API. It logs the response to the request and logs either pass or fail depending on whether the request was successful.

```
const log = require('@aws/synthetics-logger');
const AWS = require('aws-sdk');
// Require any dependencies that your script needs
// Bundle additional files and dependencies into a .zip file with folder structure
// nodejs/node_modules/additional files and folders

const basicCustomEntryPoint = async function () {

    log.info("Starting DynamoDB:listTables canary.");
    
    let dynamodb = new AWS.DynamoDB();
    var params = {};
    let request = await dynamodb.listTables(params);
    try {
        let response = await request.promise();
        log.info("listTables response: " + JSON.stringify(response));
    } catch (err) {
        log.error("listTables error: " + JSON.stringify(err), err.stack);
        throw err;
    }

    return "Successfully completed DynamoDB:listTables canary.";
};

exports.handler = async () => {
    return await basicCustomEntryPoint();
};
```

## Packaging your Node.js canary files
<a name="CloudWatch_Synthetics_Canaries_package"></a>

 **For syn-nodejs-puppeteer-11.0 and above** 

 The older packaging structure (for syn-nodejs-puppeteer-10.0 and below) is still supported in newer versions.

Create a script using one of the following options:
+ .js file (CommonJS syntax)
+ .mjs file (ES modules syntax)

For ES modules, use one of the following options:
+ .js file (CommonJS syntax)
+ .mjs file (ES modules syntax)

The package structure is defined below:
+ Root-level handler file (index.js/index.mjs)
+ Optional configuration file (synthetics.json)
+ Additional dependencies in node\$1modules (if needed)

Packaging structure example:

```
  my_function/
├── index.mjs
├── synthetics.json
├── helper-utils.mjs
└── node_modules/
    └── dependencies
```

To package, follow the steps below:

1. Install dependencies (if any).

   ```
   npm install
   ```

1. Create a .zip package.

   ```
   zip -r my_deployment_package.zip
   ```

 **For syn-nodejs-puppeteer-11.0 and below** 

The following structure is required when using Amazon S3:

```
  nodejs/
└── node_modules/
    └── myCanaryFilename.js
```

 **To add an optional sub-folder support in syn-nodejs-puppeteer-3.4\$1:** 

```
nodejs/
└── node_modules/
    └── myFolder/
        └── myCanaryFilename.js
```

**Note**  
Handler path in configuration must match your file location.

 **Handler name** 

Be sure to set your canary’s script entry point (handler) as ` myCanaryFilename.functionName` to match the file name of your script’s entry point. If you are using a runtime earlier than `syn-nodejs-puppeteer-3.4`, then `functionName` must be `handler`. If you are using ` syn-nodejs-puppeteer-3.4` or later, you can choose any function name as the handler. If you are using `syn-nodejs-puppeteer-3.4` or later, you can also optionally store the canary in a separate folder such as ` nodejs/node_modules/myFolder/my_canary_filename`. If you store it in a separate folder, specify that path in your script entry point, such as ` myFolder/my_canary_filename.functionName`.

## Changing an existing Puppeteer script to use as a Synthetics canary
<a name="CloudWatch_Synthetics_Canaries_modify_puppeteer_script"></a>

This section explains how to take Puppeteer scripts and modify them to run as Synthetics canary scripts. For more information about Puppeteer, see [Puppeteer API v1.14.0](https://github.com/puppeteer/puppeteer/blob/v1.14.0/docs/api.md). 

We'll start with this example Puppeteer script:

```
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({path: 'example.png'});

  await browser.close();
})();
```

The conversion steps are as follows:
+ Create and export a `handler` function. The handler is the entry point function for the script. If you are using a runtime earlier than ` syn-nodejs-puppeteer-3.4`, the handler function must be named `handler`. If you are using `syn-nodejs-puppeteer-3.4` or later, the function can have any name, but it must be the same name that is used in the script. Also, if you are using `syn-nodejs-puppeteer-3.4` or later, you can store your scripts under any folder and specify that folder as part of the handler name.

  ```
  const basicPuppeteerExample = async function () {};
  
  exports.handler = async () => {
      return await basicPuppeteerExample();
  };
  ```
+ Use the `Synthetics` dependency.

  ```
  var synthetics = require('@aws/synthetics-puppeteer');
  ```
+ Use the `Synthetics.getPage` function to get a Puppeteer `Page` object.

  ```
  const page = await synthetics.getPage();
  ```

  The page object returned by the Synthetics.getPage function has the ** page.on** `request`, `response` and ` requestfailed` events instrumented for logging. Synthetics also sets up HAR file generation for requests and responses on the page, and adds the canary ARN to the user-agent headers of outgoing requests on the page.

The script is now ready to be run as a Synthetics canary. Here is the updated script:

```
var synthetics = require('@aws/synthetics-puppeteer');  // Synthetics dependency

const basicPuppeteerExample = async function () {
    const page = await synthetics.getPage(); // Get instrumented page from Synthetics
    await page.goto('https://example.com');
    await page.screenshot({path: '/tmp/example.png'}); // Write screenshot to /tmp folder
};

exports.handler = async () => {  // Exported handler function 
    return await basicPuppeteerExample();
};
```

## Environment variables
<a name="CloudWatch_Synthetics_Environment_Variables"></a>

You can use environment variables when creating canaries. This allows you to write a single canary script and then use that script with different values to quickly create multiple canaries that have a similar task.

For example, suppose your organization has endpoints such as `prod`, ` dev`, and `pre-release` for the different stages of your software development, and you need to create canaries to test each of these endpoints. You can write a single canary script that tests your software and then specify different values for the endpoint environment variable when you create each of the three canaries. Then, when you create a canary, you specify the script and the values to use for the environment variables.

The names of environment variables can contain letters, numbers, and the underscore character. They must start with a letter and be at least two characters. The total size of your environment variables can't exceed 4 KB. You can't specify any Lambda reserved environment variables as the names of your environment variables. For more information about reserved environment variables, see [ Runtime environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime).

**Important**  
Environment variable keys and values are encrypted at rest using AWS owned AWS KMS keys. However, the environment variables are not encrypted on the client side. Do not store sensitive information in them.

The following example script uses two environment variables. This script is for a canary that checks whether a webpage is available. It uses environment variables to parameterize both the URL that it checks and the CloudWatch Synthetics log level that it uses. 

The following function sets `LogLevel` to the value of the ` LOG_LEVEL` environment variable.

```
 synthetics.setLogLevel(process.env.LOG_LEVEL);
```

This function sets `URL` to the value of the `URL` environment variable.

```
const URL = process.env.URL;
```

This is the complete script. When you create a canary using this script, you specify values for the `LOG_LEVEL` and `URL` environment variables.

```
var synthetics = require('@aws/synthetics-puppeteer');
const log = require('@aws/synthetics-logger');

const pageLoadEnvironmentVariable = async function () {

    // Setting the log level (0-3)
    synthetics.setLogLevel(process.env.LOG_LEVEL);
    // INSERT URL here
    const URL = process.env.URL;

    let page = await synthetics.getPage();
    //You can customize the wait condition here. For instance,
    //using 'networkidle2' may be less restrictive.
    const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000});
    if (!response) {
        throw "Failed to load page!";
    }
    //Wait for page to render.
    //Increase or decrease wait time based on endpoint being monitored.
    await page.waitFor(15000);
    await synthetics.takeScreenshot('loaded', 'loaded');
    let pageTitle = await page.title();
    log.info('Page title: ' + pageTitle);
    log.debug('Environment variable:' + process.env.URL);

    //If the response status code is not a 2xx success code
    if (response.status() < 200 || response.status() > 299) {
        throw "Failed to load page!";
    }
};

exports.handler = async () => {
    return await pageLoadEnvironmentVariable();
};
```

### Passing environment variables to your script
<a name="CloudWatch_Synthetics_Canaries_pass_variables"></a>

To pass environment variables to your script when you create a canary in the console, specify the keys and values of the environment variables in the **Environment variables** section on the console. For more information, see [Creating a canary](CloudWatch_Synthetics_Canaries_Create.md).

To pass environment variables through the API or AWS CLI, use the ` EnvironmentVariables` parameter in the `RunConfig` section. The following is an example AWS CLI command that creates a canary that uses two environment variables with keys of `Environment` and `Region`.

```
aws synthetics create-canary --cli-input-json '{
   "Name":"nameofCanary",
   "ExecutionRoleArn":"roleArn",
   "ArtifactS3Location":"s3://amzn-s3-demo-bucket-123456789012-us-west-2",
   "Schedule":{
      "Expression":"rate(0 minute)",
      "DurationInSeconds":604800
   },
   "Code":{
      "S3Bucket": "canarycreation",
      "S3Key": "cwsyn-mycanaryheartbeat-12345678-d1bd-1234-abcd-123456789012-12345678-6a1f-47c3-b291-123456789012.zip",
      "Handler":"pageLoadBlueprint.handler"
   },
   "RunConfig": {
      "TimeoutInSeconds":60,
      "EnvironmentVariables": {
         "Environment":"Production",
         "Region": "us-west-1"
      }
   },
   "SuccessRetentionPeriodInDays":13,
   "FailureRetentionPeriodInDays":13,
   "RuntimeVersion":"syn-nodejs-2.0"
}'
```

## Integrating your canary with other AWS services
<a name="CloudWatch_Synthetics_Canaries_AWS_integrate"></a>

All canaries can use the AWS SDK library. You can use this library when you write your canary to integrate the canary with other AWS services.

To do so, you need to add the following code to your canary. For these examples, AWS Secrets Manager is used as the service that the canary is integrating with.
+ Import the AWS SDK.

  ```
  const AWS = require('aws-sdk');
  ```
+ Create a client for the AWS service that you are integrating with.

  ```
  const secretsManager = new AWS.SecretsManager();
  ```
+ Use the client to make API calls to that service.

  ```
  var params = {
    SecretId: secretName
  };
  return await secretsManager.getSecretValue(params).promise();
  ```

The following canary script code snippet demonstrates an example of integration with Secrets Manager in more detail.

```
var synthetics = require('@aws/synthetics-puppeteer');
const log = require('@aws/synthetics-logger');
 
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();
 
const getSecrets = async (secretName) => {
    var params = {
        SecretId: secretName
    };
    return await secretsManager.getSecretValue(params).promise();
}
 
const secretsExample = async function () {
    let URL = "<URL>";
    let page = await synthetics.getPage();
    
    log.info(`Navigating to URL: ${URL}`);
    const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000});
    
    // Fetch secrets
    let secrets = await getSecrets("secretname")
   
    /**
    * Use secrets to login. 
    *
    * Assuming secrets are stored in a JSON format like:
    * {
    *   "username": "<USERNAME>",
    *   "password": "<PASSWORD>"
    * }
    **/
    let secretsObj = JSON.parse(secrets.SecretString);
    await synthetics.executeStep('login', async function () {
        await page.type(">USERNAME-INPUT-SELECTOR<", secretsObj.username);
        await page.type(">PASSWORD-INPUT-SELECTOR<", secretsObj.password);
        
        await Promise.all([
          page.waitForNavigation({ timeout: 30000 }),
          await page.click(">SUBMIT-BUTTON-SELECTOR<")
        ]);
    });
   
    // Verify login was successful
    await synthetics.executeStep('verify', async function () {
        await page.waitForXPath(">SELECTOR<", { timeout: 30000 });
    });
};

exports.handler = async () => {
    return await secretsExample();
};
```

## Forcing your canary to use a static IP address
<a name="CloudWatch_Synthetics_Canaries_staticIP"></a>

You can set up a canary so that it uses a static IP address.

**To force a canary to use a static IP address**

1. Create a new VPC. For more information, see [Using DNS with Your VPC](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html).

1. Create a new internet gateway. For more information, see [Adding an internet gateway to your VPC](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html#working-with-igw).

1. Create a public subnet inside your new VPC.

1. Add a new route table to the VPC.

1. Add a route in the new route table, that goes from `0.0.0.0/0` to the internet gateway.

1. Associate the new route table with the public subnet.

1. Create an elastic IP address. For more information, see [Elastic IP addresses ](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html).

1. Create a new NAT gateway and assign it to the public subnet and the elastic IP address.

1. Create a private subnet inside the VPC.

1. Add a route to the VPC default route table, that goes from `0.0.0.0/0` to the NAT gateway

1. Create your canary. 

# Writing a Python canary script
<a name="CloudWatch_Synthetics_Canaries_WritingCanary_Python"></a>

This script passes as a successful run, and returns a string. To see what a failing canary looks like, change fail = False to fail = True

```
def basic_custom_script():
    # Insert your code here
    # Perform multi-step pass/fail check
    # Log decisions made and results to /tmp
    # Be sure to wait for all your code paths to complete 
    # before returning control back to Synthetics.
    # In that way, your canary will not finish and report success
    # before your code has finished executing
    fail = False
    if fail:
        raise Exception("Failed basicCanary check.")
    return "Successfully completed basicCanary checks."
def handler(event, context):
    return basic_custom_script()
```

## Packaging your Python canary files
<a name="CloudWatch_Synthetics_Canaries_WritingCanary_Python_package"></a>

If you have more than one .py file or your script has a dependency, you can bundle them all into a single ZIP file. If you use the `syn-python-selenium-1.1` runtime, the ZIP file must contain your main canary .py file within a `python` folder, such as `python/my_canary_filename.py`. If you use ` syn-python-selenium-1.1` or later, you can optionally use a different folder , such as `python/myFolder/my_canary_filename.py`.

This ZIP file should contain all necessary folders and files, but the other files do not need to be in the `python` folder.

Be sure to set your canary’s script entry point as ` my_canary_filename.functionName` to match the file name and function name of your script’s entry point. If you are using the `syn-python-selenium-1.0` runtime, then `functionName` must be `handler`. If you are using ` syn-python-selenium-1.1` or later, this handler name restriction doesn't apply, and you can also optionally store the canary in a separate folder such as ` python/myFolder/my_canary_filename.py`. If you store it in a separate folder, specify that path in your script entry point, such as ` myFolder/my_canary_filename.functionName`. 

## Changing an existing Selenium script to use a Synthetics canary
<a name="CloudWatch_Synthetics_Canaries_WritingCanary_Python_Selenium"></a>

You can quickly modify an existing script for Python and Selenium to be used as a canary. For more information about Selenium, see [ www.selenium.dev/](https://www.selenium.dev/).

For this example, we'll start with the following Selenium script:

```
from selenium import webdriver

def basic_selenium_script():
    browser = webdriver.Chrome()
    browser.get('https://example.com')
    browser.save_screenshot('loaded.png')

basic_selenium_script()
```

The conversion steps are as follows.

**To convert a Selenium script to be used as a canary**

1. Change the `import` statement to use Selenium from the ` aws_synthetics` module:

   ```
   from aws_synthetics.selenium import synthetics_webdriver as webdriver
   ```

   The Selenium module from `aws_synthetics` ensures that the canary can emit metrics and logs, generate a HAR file, and work with other CloudWatch Synthetics features.

1. Create a handler function and call your Selenium method. The handler is the entry point function for the script.

   If you are using `syn-python-selenium-1.0`, the handler function must be named `handler`. If you are using `syn-python-selenium-1.1` or later, the function can have any name, but it must be the same name that is used in the script. Also, if you are using `syn-python-selenium-1.1` or later, you can store your scripts under any folder and specify that folder as part of the handler name.

   ```
   def handler(event, context):
       basic_selenium_script()
   ```

The script is now updated to be a CloudWatch Synthetics canary. Here is the updated script:

The `webdriver` is an instance of the class [ SyntheticsWebDriver](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library_Python.html#CloudWatch_Synthetics_Library_Python_SyntheticsWebDriver) and the browser returned by `webdriver.Chrome()` is an instance of [ SyntheticsBrowser](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library_Python.html#CloudWatch_Synthetics_Library_Python_SyntheticsBrowser).

```
from aws_synthetics.selenium import synthetics_webdriver as webdriver

def basic_selenium_script():
    browser = webdriver.Chrome()
    browser.get('https://example.com')
    browser.save_screenshot('loaded.png')

def handler(event, context):
    basic_selenium_script()
```

## Changing an existing Puppeteer Synthetics script to authenticate non-standard certificates
<a name="Canaries_Non-Standard_Certificates"></a>

One important use case for Synthetics canaries is for you to monitor your own endpoints. If you want to monitor an endpoint that isn't ready for external traffic, this monitoring can sometimes mean that you don't have a proper certificate signed by a trusted third-party certificate authority.

Two possible solutions to this scenario are as follows:
+ To authenticate a client certificate, see [ How to validate authentication using Amazon CloudWatch Synthetics – Part 2](https://aws.amazon.com/blogs/mt/how-to-validate-authentication-using-amazon-cloudwatch-synthetics-part-2/).
+ To authenticate a self-signed certificate, see [ How to validate authentication with self-signed certificates in Amazon CloudWatch Synthetics](https://aws.amazon.com/blogs/mt/how-to-validate-authentication-with-self-signed-certificates-in-amazon-cloudwatch-synthetics/)

You are not limited to these two options when you use CloudWatch Synthetics canaries. You can extend these features and add your business logic by extending the canary code.

**Note**  
Synthetics canaries running on Python runtimes innately have the ` --ignore-certificate-errors` flag enabled, so those canaries shouldn't have any issues reaching sites with non-standard certificate configurations.

# Writing a JSON configuration for Node.js multi Checks blueprint
<a name="CloudWatch_Synthetics_WritingCanary_Multichecks"></a>

The Node.js multi checks blueprint allows you to create canaries that perform multiple validation checks within a single canary run. This blueprint is useful when you want to test multiple endpoints, validate different aspects of your application, or perform a series of related checks in sequence. 

**Topics**
+ [Root configuration structure](#root-configuration-structure)
+ [Global settings](#global-settings)
+ [Variables and data management](#variables-data-management)
+ [Step definitions](#step-definitions)
+ [Check types](#check-types)
+ [Authentication methods](#authentication-methods)
+ [Assertions and validation](#assertions-validation)
+ [Data extraction](#data-extraction)

## Root configuration structure
<a name="root-configuration-structure"></a>

The root configuration defines the overall structure of your advanced API blueprint canary.


**Schema properties**  

| Property | Type | Required | Description | 
| --- | --- | --- | --- | 
|  globalSettings  | Object | No | Default configurations applied to all steps | 
|  variables  | Object | No | Reusable values across steps (max 10) | 
|  steps  | Object |  Yes  | Collection of monitoring steps (1-10 steps) | 

 **Example** 

```
{
  "globalSettings": {
    "stepTimeout": 30000,
    "userAgent": "CloudWatch-Synthetics-Advanced/1.0"
  },
  "variables": {
    "baseUrl": "https://api.example.com",
    "apiVersion": "v1"
  },
  "steps": {
    "1": {
      "stepName": "healthCheck",
      "checkerType": "HTTP",
      "url": "${baseUrl}/health",
      "httpMethod": "GET"
    }
  }
}
```

 **Validation rules** 
+ Must contain at least one step
+ Maximum 10 steps allowed
+ No additional properties allowed beyond `globalSettings`, ` variables`, and `steps`

## Global settings
<a name="global-settings"></a>

Global settings provide default configurations that apply to all steps unless overridden at the step level.

 **Properties** 


**Global setting properties**  

| Property | Type | Default | Range | Description | 
| --- | --- | --- | --- | --- | 
|  stepTimeout  | integer | 30000 | 5000-300000 | Default timeout for all steps (milliseconds) | 

 **Example** 

```
{
  "globalSettings": {
    "stepTimeout": 60000,
            
  }
}
```

## Variables and data management
<a name="variables-data-management"></a>

Variables allow you to define reusable values that can be referenced throughout your configuration using `${variableName}` syntax.

 **Variable properties** 


| Property | Type | Description | 
| --- | --- | --- | 
| Variable names | string | Must match pattern ^[a-zA-Z][a-zA-Z0-9\$1]\$1\$1 | 
| Variable values | string | Any string value | 

 **Limitations** 
+ Maximum 10 variables per configuration
+ Variable names must start with a letter
+ Variable names can contain letters, numbers, and underscores only
+ Maximum length not specified in schema

 **Example** 

```
{
  "variables": {
    "baseUrl": "https://api.example.com",
    "apiKey": "${AWS_SECRET:my-api-key}",
    "timeout": "30000",
    "userEmail": "test@example.com"
  }
}
```

 **Configuration usage** 

```
{
  "steps": {
    "1": {
      "url": "${baseUrl}/users",
      "timeout": "${timeout}",
      "headers": {
        "Authorization": "Bearer ${apiKey}"
      }
    }
  }
}
```

## Step definitions
<a name="step-definitions"></a>

Steps define individual monitoring operations. Each step is numbered from 1 to 10 and contains a specific type of check.

 *Common step properties* 


| Property | Type | Required | Description | 
| --- | --- | --- | --- | 
|  stepName  | string |  Yes  | Unique identifier for the step | 
|  checkerType  | string |  Yes  | Type of check: HTTP, DNS, SSL,  TCP | 
|  extractors  | array | No | Data extraction configuration | 

 *Step name validation* 
+ Pattern - ^[a-zA-Z][a-zA-Z0-9\$1-]\$1\$1
+ Maximum length - 64 characters
+ Must start with a letter

 *Step numbering* 
+ Steps are numbered as string keys: "1", "2", ..., "10"
+ Pattern: ^([1-9]\$110)\$1
+ Minimum 1 step required
+ Maximum 10 steps allowed

 *Example* 

```
{
  "steps": {
    "1": {
      "stepName": "loginAPI",
      "checkerType": "HTTP",
      "url": "https://api.example.com/login",
      "httpMethod": "POST"
    },
    "2": {
      "stepName": "dnsCheck",
      "checkerType": "DNS",
      "domain": "example.com"
    }
  }
}
```

## Check types
<a name="check-types"></a>

### HTTP checks
<a name="http-types"></a>

Monitor web endpoints and APIs with comprehensive request and response validation.

 **Required properties** 


| Property | Type | Description | 
| --- | --- | --- | 
|  url  | string | Target URL (must be valid URI format) | 
|  httpMethod  | string | HTTP method: GET, POST, PUT,  PATCH, DELETE, HEAD, OPTIONS | 

 **Optional properties** 


| Property | Type | Default | Range | Description | 
| --- | --- | --- | --- | --- | 
|  timeout  | integer | 30000 | 5000-300000 | Request timeout (milliseconds) | 
|  waitTime  | integer | 0 | 0-60 | Delay before request (seconds) | 
|  headers  | object | - | - | Custom HTTP headers | 
|  body  | string | - | - | Request body for POST/PUT operations | 
|  authentication  | object | - | - | Authentication configuration | 
|  assertions  | array | - | - | Response validation rules | 

 **Example** 

```
{
  "stepName": "createUser",
  "checkerType": "HTTP",
  "url": "https://api.example.com/users",
  "httpMethod": "POST",
  "timeout": 15000,
  "headers": {
    "Content-Type": "application/json",
    "X-API-Version": "v1"
  },
  "body": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}",
  "authentication": {
    "type": "API_KEY",
    "apiKey": "${AWS_SECRET:api-credentials}",
    "headerName": "X-API-Key"
  },
  "assertions": [
    {
      "type": "STATUS_CODE",
      "operator": "EQUALS",
      "value": 201
    }
  ]
}
```

### DNS checks
<a name="dns-types"></a>

Validate DNS resolution and record information.

 **Required properties** 


| Property | Type | Description | 
| --- | --- | --- | 
|  domain  | string | Domain name to query (hostname format) | 

 **Optional properties** 


| Property | Type | Default | Description | 
| --- | --- | --- | --- | 
|  recordType  | string | "A" | DNS record type: A, CNAME, MX,  TXT, NS | 
|  nameserver  | string | - | Specific DNS server to query | 
|  timeout  | integer | 30000 | Query timeout (5000-300000ms) | 
|  port  | integer | 53 | DNS server port (1-65535) | 
|  protocol  | string | "UDP" | Protocol: UDP or TCP | 
|  assertions  | array | - | DNS response validation rules | 

 **Example** 

```
{
  "stepName": "dnsResolution",
  "checkerType": "DNS",
  "domain": "example.com",
  "recordType": "A",
  "nameserver": "8.8.8.8",
  "timeout": 10000,
  "assertions": [
    {
      "type": "RECORD_VALUE",
      "operator": "CONTAINS",
      "value": "192.168"
    }
  ]
}
```

### SSL checks
<a name="ssl-types"></a>

Monitor SSL certificate health and configuration.

 **Required properties** 


| Property | Type | Description | 
| --- | --- | --- | 
|  hostname  | string | Target hostname (hostname format) | 

 **Optional properties** 


| Property | Type | Default | Description | 
| --- | --- | --- | --- | 
|  port  | integer | 443 | SSL port (1-65535) | 
|  timeout  | integer | 30000 | Connection timeout (5000-300000ms) | 
|  sni  | boolean | TRUE | Server Name Indication | 
|  verifyHostname  | boolean | TRUE | Hostname verification | 
|  allowSelfSigned  | boolean | FALSE | Accept self-signed certificates | 
|  assertions  | array | - | Certificate validation rules | 

 **Example** 

```
{
  "stepName": "sslCertCheck",
  "checkerType": "SSL",
  "hostname": "secure.example.com",
  "port": 443,
  "sni": true,
  "verifyHostname": true,
  "assertions": [
    {
      "type": "CERTIFICATE_EXPIRY",
      "operator": "GREATER_THAN",
      "value": 30,
      "unit": "DAYS"
    }
  ]
}
```

### TCP checks
<a name="tcp-types"></a>

Test TCP port connectivity and response validation.

 **Required properties** 


| Property | Type | Description | 
| --- | --- | --- | 
|  hostname  | string | Target hostname (hostname format) | 
|  port  | integer | Target port (1-65535) | 

 **Optional properties** 


| Property | Type | Default | Description | 
| --- | --- | --- | --- | 
|  timeout  | integer | 30000 | Overall timeout (5000-300000ms) | 
|  connectionTimeout  | integer | 3000 | Connection timeout (5000-300000ms) | 
|  readTimeout  | integer | 2000 | Data read timeout (5000-300000ms) | 
|  sendData  | string | - | Data to send after connection | 
|  expectedResponse  | string | - | Expected response data | 
|  encoding  | string | "UTF-8" | Data encoding: UTF-8, ASCII, HEX | 
|  assertions  | array | - | Connection and response validation | 

 **Example** 

```
{
  "stepName": "databaseConnection",
  "checkerType": "TCP",
  "hostname": "db.example.com",
  "port": 3306,
  "connectionTimeout": 5000,
  "sendData": "SELECT 1",
  "expectedResponse": "1",
  "assertions": [
    {
      "type": "CONNECTION_SUCCESSFUL",
      "value": true
    }
  ]
}
```

## Authentication methods
<a name="authentication-methods"></a>

 **No authentication** 

```
{
  "type": "NONE"
}
```

 **Basic authentication** 


| Property | Type | Required | Description | 
| --- | --- | --- | --- | 
|  type  | string |  Yes  | Must be "BASIC" | 
|  username  | string |  Yes  | Username for authentication | 
|  password  | string |  Yes  | Password for authentication | 

 **Example** 

```
{
  "type": "BASIC",
  "username": "admin",
  "password": "${AWS_SECRET:basic-auth:password}"
}
```

 **API key authentication** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "API\$1KEY" | 
|  apiKey  | string |  Yes  | - | API key value | 
|  headerName  | string | No | "X-API-Key" | Header name for API key | 

 **Example** 

```
{
  "type": "API_KEY",
  "apiKey": "${AWS_SECRET:api-credentials}",
  "headerName": "Authorization"
}
```

 **OAuth client credentials** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "OAUTH\$1CLIENT\$1CREDENTIALS" | 
|  tokenUrl  | string |  Yes  | - | OAuth token endpoint URL | 
|  clientId  | string |  Yes  | - | OAuth client ID | 
|  clientSecret  | string |  Yes  | - | OAuth client secret | 
|  scope  | string | No | - | OAuth scope | 
|  audience  | string | No | - | OAuth audience | 
|  resource  | string | No | - | OAuth resource | 
|  tokenApiAuth  | array | No | - | Token API auth methods: BASIC\$1AUTH\$1HEADER, REQUEST\$1BODY | 
|  tokenCacheTtl  | integer | No | 3600 | Token cache TTL (minimum 60 seconds) | 

 **Example** 

```
{
  "type": "OAUTH_CLIENT_CREDENTIALS",
  "tokenUrl": "https://auth.example.com/oauth/token",
  "clientId": "${AWS_SECRET:oauth-creds:client_id}",
  "clientSecret": "${AWS_SECRET:oauth-creds:client_secret}",
  "scope": "read write",
  "tokenCacheTtl": 7200
}
```

 **AWS Signature (Version 4)** 


| Property | Type | Required | Description | 
| --- | --- | --- | --- | 
|  type  | string |  Yes  | Must be "SIGV4" | 
|  service  | string |  Yes  | Name of the AWS service (for example, "execute-api") | 
|  region  | string |  Yes  | AWS region | 
|  roleArn  | string |  Yes  | IAM role ARN for signing | 

 **Example** 

```
{
  "type": "SIGV4",
  "service": "execute-api",
  "region": "us-east-1",
  "roleArn": "arn:aws:iam::123456789012:role/SyntheticsRole"
}
```

## Assertions and validation
<a name="assertions-validation"></a>

### HTTP assertions
<a name="http-assertions"></a>

 **Status code assertions** 


| Property | Type | Required | Description | 
| --- | --- | --- | --- | 
|  type  | string |  Yes  | Must be "STATUS\$1CODE" | 
|  operator  | string |  Yes  | EQUALS, NOT\$1EQUALS, GREATER\$1THAN,  LESS\$1THAN, IN\$1RANGE | 
|  value  | integer | Conditional | HTTP status code (100-599) | 
|  rangeMin  | integer | Conditional | Minimum range value (for IN\$1RANGE) | 
|  rangeMax  | integer | Conditional | Maximum range value (for IN\$1RANGE) | 

```
{
  "type": "STATUS_CODE",
  "operator": "EQUALS",
  "value": 200
}
```

 **Response time assertions** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "RESPONSE\$1TIME" | 
|  operator  | string |  Yes  | - | LESS\$1THAN, GREATER\$1THAN, EQUALS | 
|  value  | number |  Yes  | - | Time value (minimum 0) | 
|  unit  | string | No | "MILLISECONDS" | Must be "MILLISECONDS" | 

```
{
  "type": "RESPONSE_TIME",
  "operator": "LESS_THAN",
  "value": 500,
  "unit": "MILLISECONDS"
}
```

 **Head assertions** 


| Property | Type | Required | Description | 
| --- | --- | --- | --- | 
|  type  | string |  Yes  | Must be "HEADER" | 
|  headerName  | string |  Yes  | Name of header to validate | 
|  operator  | string |  Yes  | EQUALS, NOT\$1EQUALS, CONTAINS,  NOT\$1CONTAINS, REGEX\$1MATCH, EXIST | 
|  value  | string/boolean | Conditional | Expected value (boolean for EXIST operator) | 

```
{
  "type": "HEADER",
  "headerName": "Content-Type",
  "operator": "CONTAINS",
  "value": "application/json"
}
```

 **Body assertions** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "BODY" | 
|  target  | string | No | "JSON" | JSON or TEXT | 
|  path  | string | Conditional | - | JSONPath (required for JSON target) | 
|  operator  | string |  Yes  | - | CONTAINS, NOT\$1CONTAINS, EQUALS,  NOT\$1EQUALS, EXISTS | 
|  value  | string/boolean |  Yes  | - | Expected value (boolean for EXISTS operator) | 

```
{
  "type": "BODY",
  "target": "JSON",
  "path": "$.users[0].name",
  "operator": "EQUALS",
  "value": "John Doe"
}
```

### DNS assertions
<a name="dns-assertions"></a>

 **Record value assertions** 


| Property | Type | Required | Range | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "RECORD\$1VALUE" | 
|  operator  | string |  Yes  | - | EQUALS, NOT\$1EQUALS, CONTAINS,  NOT\$1CONTAINS, REGEX\$1MATCH | 
|  value  | string |  Yes  | - | Expected record value | 

 **Record count assertions** 


| Property | Type | Required | Range | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "RECORD\$1COUNT" | 
|  operator  | string |  Yes  | - | EQUALS, GREATER\$1THAN, LESS\$1THAN | 
|  value  | integer |  Yes  | ≥ 0 | Expected count (minimum 0) | 

 **Authoritative assertions** 


| Property | Type | Required | Range | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "AUTHORITATIVE" | 
|  value  | boolean |  Yes  | - | Expected authoritative status | 

 **TTL assertions** 


| Property | Type | Required | Range | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "TTL" | 
|  operator  | string |  Yes  | - | EQUALS, GREATER\$1THAN, LESS\$1THAN | 
|  value  | integer |  Yes  | ≥ 0 | Expected TTL (minimum 0) | 

### SSL assertions
<a name="ssl-assertions"></a>

 **Certificate expiry assertions** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "CERTIFICATE\$1EXPIRY" | 
|  operator  | string |  Yes  | - | GREATER\$1THAN, LESS\$1THAN | 
|  value  | integer |  Yes  | - | Time value (minimum 0) | 
|  unit  | string | No | "DAYS" | DAYS, HOURS | 

 **Certificate subject assertions** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "CERTIFICATE\$1SUBJECT" | 
|  field  | string |  Yes  | - | Subject field: CN, O, OU, C , ST, L | 
|  operator  | string |  Yes  | - | CONTAINS, EQUALS, REGEX\$1MATCH | 
|  value  | string |  Yes  | - | Expected field value | 

 **Certificate issuer assertions** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "CERTIFICATE\$1ISSUER" | 
|  field  | string |  Yes  | - | Issuer field: CN, O | 
|  operator  | string |  Yes  | - | CONTAINS, EQUALS | 
|  value  | string |  Yes  | - | Expected field value | 

### TCP assertions
<a name="tcp-assertions"></a>

 **Connection success assertions** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "CONNECTION\$1SUCCESSFUL" | 
|  value  | boolean |  Yes  | - | Expected connection status | 

 **Response data assertions** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  type  | string |  Yes  | - | Must be "RESPONSE\$1DATA" | 
|  operator  | string |  Yes  | - | CONTAINS, EQUALS, NOT\$1CONTAINS,  REGEX\$1MATCH, STARTS\$1WITH, ENDS\$1WITH | 
|  value  | string |  Yes  | - | Expected response data | 
|  encoding  | string | No | "UTF-8" | UTF-8, ASCII, HEX | 

## Data extraction
<a name="data-extraction"></a>

Extractors allows you to capture data from responses for use in subsequent steps or for reporting purposes.

 **Extraction properties** 


| Property | Type | Required | Default | Description | 
| --- | --- | --- | --- | --- | 
|  name  | string |  Yes  | - | Variable name for extracted data | 
|  type  | string |  Yes  | - | Extraction type: BODY | 
|  path  | string | No | - | JSONPath for body extraction | 
|  regex  | string | No | - | Regular expression pattern | 
|  regexGroup  | integer | No | 0 | Regex capture group (minimum 0) | 

 **Extraction name validation** 
+ Pattern: `^[a-zA-Z][a-zA-Z0-9_]*$`
+ Must start with a letter
+ Can contain letters, numbers, and underscores

**Limitation** – Substitution does not apply for fields in the schema that have specific ENUM values

 **Extraction types** 

```
{
  "name": "userId",
  "type": "BODY",
  "path": "$.user.id"
}
```

```
{
  "stepName": "loginAndExtract",
  "checkerType": "HTTP",
  "url": "https://api.example.com/login",
  "httpMethod": "POST",
  "body": "{\"username\":\"test\",\"password\":\"pass\"}",
  "extractors": [
    {
      "name": "textVariable",
      "type": "BODY",
      "path": "$.myvalue"
    }
  ]
},
{
  "stepName": "substituteVariable",
  "checkerType": "HTTP",
  "url": "https://api.example.com/get/${textVariable}",
  "httpMethod": "GET",
  "assertions": [
    {
    "type": "BODY",
    "target": "JSON",
    "path": "$.users[0].name",
    "operator": "EQUALS",
    "value": "${textVariable}"
    }
  ]
}
```