# Jenkins Freestyle Integration with Ansible and Hivel Webhook

This guide walks you through integrating Jenkins Freestyle pipelines with Hivel using an Ansible playbook and webhook reporting. You’ll learn how to trigger a playbook, capture build and stage metadata, and send it to Hivel’s API endpoint for monitoring and analytics.

### 🧰 Prerequisites

Make sure the following are set up on your Jenkins server (typically an EC2 instance):

* ✅ Jenkins installed and accessible
* ✅ Admin access to Jenkins UI
* ✅ Ansible installed and available in PATH
* ✅ Git configured (for Git metadata collection)

#### 📦 Required Plugin

Install the following plugin:

| Plugin Name                                                             | Description                                 |
| ----------------------------------------------------------------------- | ------------------------------------------- |
| [Groovy Postbuild Plugin](https://plugins.jenkins.io/groovy-postbuild/) | Allows running Groovy scripts after a build |

### **Required Credentials for Integration** <a href="#required-credentials-for-integration" id="required-credentials-for-integration"></a>

During the integration, you'll need:

* **Organization ID** (Generated in the Hivel application)
* **API Key** (Generated in the Hivel application)

### Retrieving Your Organization ID and API Key <a href="#retrieving-your-organization-id-and-api-key" id="retrieving-your-organization-id-and-api-key"></a>

1. Open the Hivel application and navigate to **Integrations** under the **Settings**.
2. Locate the Jenkins Integration card and click **Connect**.

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcZYmHucOE6nqrdMPazoVFZu-2by7ZZRz5pw6U72JOU-3o6uwVU_5p9pZnk_SZ1qicp8BS2u825cIjAvYuPfVhz_kLN_EtVuiBbgodCbT03dwU9uPjxMIQc4oQ68HHC3R8CL66O1w%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=54cb567c&#x26;sv=2" alt="" width="188"><figcaption></figcaption></figure>
3. Follow the given steps to start your integration.

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdFDwkQcmeDCx88WyPCRtUng-239XHII5rHC6lD1Gxgb87qXLsjMSyLZyYEovZ4nbjkkGWGiyKk3QkJ8_4AVbzrOYNmtQ7Q1v5_nSPsU0zFPndGMxs-vzQkgfG-piYsoz-akJAkDA%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=58fa4ce6&#x26;sv=2" alt="" width="375"><figcaption></figcaption></figure>
4. By clicking on generate your **Organization ID** and **API Key** will be generated.

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc4tfb0nUfcb7DYhWE1bhXNm18Z8Vz3DRC13PWMrbPH72uuUdTPYM1EcKHe5G4aMonV1OxmrYfUm2bDvzOrf2jYGuKNhTu0BJ8vYqAyVKM3L71taOLTgqHjJPDzxO2Gm55ycZdjqg%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=d942c601&#x26;sv=2" alt="" width="375"><figcaption></figcaption></figure>
5. Use these fields while integrating with Jenkins. The steps for where to apply them are provided on the following page.
6. If you find it difficult, don’t worry! You can always reach out to us for assistance at <support@hivel.ai>.

### Configuring Credentials in Jenkins <a href="#configuring-credentials-in-jenkins" id="configuring-credentials-in-jenkins"></a>

1. Log in to your Jenkins application.
2. From the side navigation, select **Manage Jenkins**.

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc7Zq7YEYzRD-WXeVGhLXREZ0x-wPNePtZV2Y96aQzAZFGMl_EIvlNQH9prvjv572f47Sa9F-83dLSjfHKbWtDSr7cPTV4KtauVs9Ka1wlMUVzjE52uefqJ5ep8EweRFW48qHLJxg%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=58f52d92&#x26;sv=2" alt="" width="188"><figcaption></figcaption></figure>
3. Click on **Credentials**.

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXffr4HxN9iKLh6eDudRikfP5RybAw1E-yIAfhdJOJubPXugW0xbAajKpYunI_W_SDHg05fyTZU8N47NCiLFhkzQ1vb_buv8NsyHT6QYekGkhDd3pwqiNEVQZCHnos6QzzjyHHQc%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=fb96b6e4&#x26;sv=2" alt="" width="563"><figcaption></figcaption></figure>
4. Click on **global** under **Stores scoped to Jenkins**

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdwGJ-s8Anoz78DyD3pVVk3u7vDQhi8cE_iMTQs-5S-CU6bjN4ZyQ6GLNLCEVr8Jc8X9pJAtydp9-GcshQlm2iohWLd6Q8BkiAZQ_z6SnamHBkdspuF86xKxvZxk0oDxX5DSv1KZQ%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=b6a1d3c4&#x26;sv=2" alt="" width="563"><figcaption></figcaption></figure>
5. Choose **Add Credentials** - you will need to do this **twice** (once for the **Organization ID** and once for the **API Key**).

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXflHCv0b6EEEdh_7QRCFuPuaxkoLWeoAV2Y2aQSXrC1JrXfx-r-JV_2WL5fmDfDLvVqW48CvgpdeyZHgVLfpK77ASSbOm1Mt4cYoT-luyzJMB6168R9dyRR8FYUCVQqNAz00SftnA%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=9b516f09&#x26;sv=2" alt="" width="563"><figcaption></figcaption></figure>
6. From the dropdown, select **Secret text**.

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfkFiohkzASOjDeYAK7CRjetdJpMX7szdwBuBwiOlRFW2RZex0NRmb8dmyly0tXLj2HhkefIRtRnGzj1e84pWjcakRqXl-tYE1azMtd9SHJ7BsozEZEc3Kqvz1E9q2V0XeAmD79yw%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=5cf6b250&#x26;sv=2" alt="" width="563"><figcaption></figcaption></figure>
7. After selecting Secret text from the dropdown, fill in the details twice as follows:
   1. **Adding the API Key**
      1. **ID**: hivel-jenkins-api-key
      2. **Secret**: {your API key} (copied from Hivel)
      3. **Description**: This is the Jenkins API key from Hivel
      4. Click **Save**
   2. **Repeat the process again for Adding the Organization ID**
      1. **ID**: hivel-org-id
      2. **Secret**: {your Organization ID} (copied from Hivel)
      3. **Description**: This is the Jenkins Organization ID from Hivel
      4. Click **Save**
8. Once you have completed the above steps, your Jenkins Global Credentials should display the following entries:

   <figure><img src="https://docs.hivel.ai/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfWdtQhOLtex0MnuuSI0ZzdObsS2ND_SoptxoJJqAwFBwiXfE7cBRtijuz07ZPiU3R26HTqWV_FMG-DgoGaBdud3ezSoQR-FeDLhRCfMSvu4Oe49cHdWUV6ZKAqmtsiBbaALk9tUQ%3Fkey%3DGGAZqg4Zxvg8E9x8twDGRtGG&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=db663b15&#x26;sv=2" alt="" width="563"><figcaption></figcaption></figure>

### Step 2: Bind Secrets in Job Configuration

1. Open your Freestyle job(pipeline in your dashboards)
2. Go to the Environment section
3. Check Use secret text(s) or file(s)
4. Click Add and bind the credentials like this:

| Variable Name            | Credentials ID        |
| ------------------------ | --------------------- |
| HIVEL\_ORG\_ID           | hivel-org-id          |
| HIVEL\_JENKINS\_API\_KEY | hivel-jenkins-api-key |

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfrROD4fE3ztbnLW_lKliQIC-OUBbnAt80C00w9axlCua8yfs630ObHaRnpCOi5zLgxtxYTjr8UNEC7TTiz0PwsOeTkPjtD990kmn_MotO8d2omSmDp7L3Bwrw_FAuZcMd2bDCAOQ?key=sptM1z_vQZxujavWJbcjIjix" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfoRnoCqHFLOKxI7-7ALJ3O-WTyLo9FXSbLYeNPiQq80hoZ71aMWLKDN_zj3fgTa5HAKyDDnxGKfajM57oaBFR9_lRFwYJyZouzwNnTyeMzKmd1JdEtlDHEC9yFdsQONI6ZPsj6HA?key=sptM1z_vQZxujavWJbcjIjix" alt="" width="563"><figcaption></figcaption></figure>

### Step 3: Install Ansible on Jenkins Server

Ensure Ansible is installed on the EC2/server where Jenkins is hosted.

For Amazon Linux 2:

```sh
sudo amazon-linux-extras enable ansible2
sudo yum install -y ansible
```

Run ansible --version on the Jenkins server to confirm installation.

### Step 4: Add Build Step – Execute Ansible Playbook

Under Build Steps → Execute shell, add:

```sh
# Run Ansible playbook with JSON output
ANSIBLE_STDOUT_CALLBACK=json \
ansible-playbook ansible/dummy-stages.yml \
-i "localhost," --connection=local \
> ansible-output.json 2>&1 || true
```

This ensures that:

* The playbook runs locally
* Output is saved in JSON format to ansible-output.json
* Even on errors, the script doesn’t fail the job

{% hint style="info" %}
**Note** : replace the path of your ansible file with **“ansible/dummy-stages.yml”**. Invoking ansible like this will only help us to get insights from ansible playbook.
{% endhint %}

### Step 5: Add Groovy Postbuild Script for Webhook Reporting

Under Post-build Actions:

1. Add → Groovy Postbuild
2. Paste the Groovy webhook script (Full Version) ([**provided below**](#groovy-postbuild-script))
3. Uncheck the box labeled Use Groovy Sandbox
4. Approve script (open will appear once you enter script in groovy text box)
5. Save the job

{% hint style="warning" %}
if you check **“use groovy sandbox”** you might require to allow all script to be executed from settings section : **go to Manage Jenkins → In-process Script Approval** and approve the pending script.
{% endhint %}

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXc2hD87gT-LKfnOrsWS6c7IPVq4dxoQpAv770wGpwnUpq1I_bLGWeiZNlsDRBSsVKKOwYAqEWQa9BU3WjA347652MXAYD1q2wEjkCXbXzX9XVKXGra4zxKxp2w-oN9PUNMkdKx1?key=sptM1z_vQZxujavWJbcjIjix" alt="" width="563"><figcaption></figcaption></figure>

Add groovy postbuild

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXedb64AapOp5gisXgtrxHwG0BTmI6fBsWqfhxl4VYSgxzBwFKObH8BMfWs5DC_7H4-WVYYBQ0FHB-reo_3WPNn6TErLvm0nuljDZJmTlOLGPigO1DxAkalPpvkKI-CZzXUvFiln?key=sptM1z_vQZxujavWJbcjIjix" alt="" width="563"><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Sample Webhook Payload Sent" %}

```javascript
{
  "org_id": 12345,
  "job_name": "freestyle-ansible-job",
  "build_number": 12,
  "status": "SUCCESS",
  "build_url": "https://jenkins.example.com/job/freestyle-ansible-job/12/",
  "start_time": "2025-04-08T10:12:00Z",
  "end_time": "2025-04-08T10:12:45Z",
  "duration": 45.25,
  "git": {
    "commit": "abc123...",
    "branch": "main",
    "author": "John Doe",
    "author_email": "john@example.com",
    "commit_message": "Add API integration",
    "repository": "git@example.com:project/repo.git"
  },
  "triggered_by": "john.doe",
  "stages": [
    {
      "stage_name": "Ensure packages installed",
      "start_time": "2025-04-08T10:12:05.000000Z",
      "end_time": "2025-04-08T10:12:10.000000Z",
      "duration": 5.0,
      "status": "SUCCESS",
      "message": "All packages present"
    }
  ]
}

```

{% endtab %}

{% tab title="Groovy Postbuild Script" %}

```javascript
/**
* JenkinsBuildData class handles all build data collection and processing
* for Jenkins freestyle jobs. It collects build metadata, Git information,
* and stage execution details, then sends them to a webhook endpoint.
*
* This script is designed to work with Jenkins freestyle jobs and expects:
* 1. A workspace directory with Git repository
* 2. Ansible output file (ansible-output.json) in the workspace
* 3. Environment variables for configuration
*/
import hudson.model.Cause
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import java.text.SimpleDateFormat
import java.util.TimeZone

class JenkinsBuildData {

   def env
   def manager
   def workspace
   def logger

   // API endpoint configuration
   static final String API_ENDPOINT = 'https://app.hivel.ai/insightlyapi/webhook/jenkins'
   static final int HTTP_TIMEOUT = 5000
   static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
   static final String DATE_FORMAT_SIMPLE = "yyyy-MM-dd'T'HH:mm:ss'Z'"

   /**
    * Constructor initializes the build data collector with Jenkins environment
    * @param manager Jenkins build manager instance
    * @throws IllegalArgumentException if manager is null
    */
   JenkinsBuildData(manager) {
       if (!manager) {
           throw new IllegalArgumentException('Manager cannot be null')
       }
       this.manager = manager
       this.env = manager.build.environment
       this.workspace = new File(this.env.WORKSPACE)
       this.logger = manager.listener.logger
   }

   /**
    * Validates the workspace and required environment variables
    * @return boolean indicating if all required conditions are met
    */
   private boolean validateEnvironment() {
       if (!workspace.exists()) {
           logger.println "Error: Workspace directory does not exist: ${workspace.absolutePath}"
           return false
       }

       if (!env.JOB_NAME) {
           logger.println 'Error: JOB_NAME environment variable is not set'
           return false
       }

       if (!env.BUILD_NUMBER) {
           logger.println 'Error: BUILD_NUMBER environment variable is not set'
           return false
       }

       return true
   }

   /**
    * Calculates duration between two timestamps in seconds with 2 decimal precision
    * @param start Start timestamp
    * @param end End timestamp
    * @return Duration in seconds
    */
   private double calculateDuration(String start, String end) {
       if (!start || !end) return 0
       try {
           def sdf = new SimpleDateFormat(DATE_FORMAT)
           sdf.setTimeZone(TimeZone.getTimeZone('UTC'))
           def startDate = sdf.parse(start)
           def endDate = sdf.parse(end)
           def durationMs = endDate.time - startDate.time
           // Convert milliseconds to seconds and round to 2 decimal places
           return (durationMs / 1000.0).round(2)
       } catch (Exception e) {
           return 0
       }
   }

   /**
    * Parses build stages from ansible-output.json file
    * @return List of stage objects containing execution details
    */
   def parseAnsibleStages() {
       def stages = []
       try {
           def ansibleOutputFile = new File(workspace.toString() + '/ansible-output.json')
           if (!ansibleOutputFile.exists() || !ansibleOutputFile.text?.trim()) {
               return stages
           }

           def jsonContent = ansibleOutputFile.text.substring(
               ansibleOutputFile.text.indexOf('{'),
               ansibleOutputFile.text.lastIndexOf('}') + 1
           )
           def ansibleOutput = new JsonSlurper().parseText(jsonContent)

           if (!ansibleOutput.plays || !ansibleOutput.plays[0]?.tasks) {
               return stages
           }

           ansibleOutput.plays[0].tasks.each { task ->
               def hostInfo = task.hosts?.localhost
               if (!hostInfo || !(hostInfo.action in ['debug', 'fail'])) return

               def durationMs = calculateDuration(task.task.duration?.start, task.task.duration?.end)
               stages << [
                   stage_name : task.task.name ?: 'Unnamed Task',
                   start_time : task.task.duration?.start ?: '',
                   end_time   : task.task.duration?.end ?: '',
                   duration   : durationMs,
                   status     : determineTaskStatus(hostInfo),
                   message    : hostInfo.msg ?: hostInfo.message ?: 'No message provided'
               ]
           }
       } catch (Exception e) { }
       return stages
   }

   /**
    * Determines the status of a task based on its execution result
    * @param hostInfo Task execution information
    * @return Status string (FAILED, SKIPPED, CHANGED, SUCCESS, or UNKNOWN)
    */
   private String determineTaskStatus(hostInfo) {
       if (hostInfo.failed == true) return 'FAILED'
       if (hostInfo.skipped == true) return 'SKIPPED'
       if (hostInfo.changed == true) return 'CHANGED'
       if (hostInfo.failed != true && hostInfo.skipped != true) return 'SUCCESS'
       return 'UNKNOWN'
   }

   /**
    * Calculates build timing information
    * @return Map containing start_time, end_time (ISO8601 format), and duration (in seconds)
    */
   def getBuildTiming() {
       def startTime = manager.build.getStartTimeInMillis()
       def duration = manager.build.getDuration()
       def endTime = startTime + duration

       // If duration is 0, calculate from start time to current time
       if (duration == 0) {
           endTime = System.currentTimeMillis()
           duration = endTime - startTime
       }

       // Calculate duration in seconds and round to 2 decimal places
       def durationSec = Math.round(duration / 1000.0 * 100) / 100.0

       def dateFormat = new SimpleDateFormat(DATE_FORMAT_SIMPLE)
       dateFormat.setTimeZone(TimeZone.getTimeZone('UTC'))

       return [
           start_time: dateFormat.format(new Date(startTime)),
           end_time  : dateFormat.format(new Date(endTime)),
           duration  : durationSec
       ]
   }

   /**
    * Collects Git repository information
    * @return Map containing Git metadata (commit, branch, author, etc.)
    */
   def getGitInfo() {
       def gitCommit = env.GIT_COMMIT ?: executeCommand('git rev-parse HEAD')
       def gitBranch = (env.GIT_BRANCH ?: executeCommand('git rev-parse --abbrev-ref HEAD'))?.replaceAll('^origin/', '')
       def gitRepo = env.GIT_URL ?: executeCommand('git config --get remote.origin.url')

       def commitDetails = executeCommand("git log -1 --pretty=format:'%an|%ae|%s'").split('\\|', 3)

       return [
           commit        : gitCommit,
           branch        : gitBranch,
           author        : commitDetails.size() > 0 ? commitDetails[0].trim() : '',
           author_email  : commitDetails.size() > 1 ? commitDetails[1].trim() : '',
           commit_message: commitDetails.size() > 2 ? commitDetails[2].trim() : '',
           repository    : gitRepo
       ]
   }

   /**
    * Executes a shell command in the workspace directory
    * @param command Shell command to execute
    * @return Command output as string
    */
   def executeCommand(command) {
       try {
           def process = command.execute(null, workspace)
           process.waitFor()
           return process.text.trim()
       } catch (Exception e) {
           return ''
       }
   }

   /**
    * Determines who triggered the build
    * @return Username of the build triggerer
    */
   def getBuildTrigger() {
       def cause = manager.build.getCause(Cause.UserIdCause)
       return cause?.userName ?: 'Unknown'
   }

   /**
    * Builds the complete payload for the webhook
    * @param stages List of build stages
    * @return Complete payload map ready for JSON conversion
    */
   def buildPayload(stages) {
       def timing = getBuildTiming()
       def gitInfo = getGitInfo()

       return [
           org_id       : (env.HIVEL_ORG_ID ?: '0') as int,
           job_name     : env.JOB_NAME,
           build_number : env.BUILD_NUMBER as int,
           status       : manager.build.result.toString(),
           build_url    : env.BUILD_URL,
           start_time   : timing.start_time,
           end_time     : timing.end_time,
           duration     : timing.duration,
           git          : gitInfo,
           triggered_by : getBuildTrigger(),
           stages       : stages
       ]
   }

   /**
    * Sends the payload to the configured webhook endpoint
    * @param jsonPayload JSON string to send
    * @return boolean indicating if the request was successful
    */
   def sendHttpRequest(jsonPayload) {
       if (!jsonPayload) {
           logger.println 'Error: Cannot send empty payload'
           return false
       }

       def url = new URL(API_ENDPOINT)
       def conn = url.openConnection()

       try {
           conn.setRequestMethod('POST')
           conn.doOutput = true
           conn.setRequestProperty('Content-Type', 'application/json')

           def apiKey = env.HIVEL_JENKINS_API_KEY
           if (apiKey) {
               conn.setRequestProperty('API_KEY', apiKey)
           } else {
               logger.println 'Warning: HIVEL_JENKINS_API_KEY is not set'
           }

           conn.setConnectTimeout(HTTP_TIMEOUT)
           conn.setReadTimeout(HTTP_TIMEOUT)

           conn.outputStream.withWriter('UTF-8') { writer ->
               writer.write(jsonPayload)
           }

           def responseCode = conn.responseCode
           def responseText = conn.inputStream.text
           logger.println "Webhook response code: $responseCode"
           logger.println "Webhook response: $responseText"

           return responseCode >= 200 && responseCode < 300
       } catch (Exception e) {
           logger.println "Error sending request: ${e.message}"
           logger.println "Stack trace: ${e.stackTrace}"
           return false
       }
   }

}

// Main execution block
try {
   def buildData = new JenkinsBuildData(manager)

   // Validate environment before proceeding
   if (!buildData.validateEnvironment()) {
       throw new IllegalStateException('Environment validation failed')
   }

   def stages = buildData.parseAnsibleStages()
   def payload = buildData.buildPayload(stages)
   def jsonPayload = JsonOutput.toJson(payload)

   if (!buildData.sendHttpRequest(jsonPayload)) {
       throw new IllegalStateException('Failed to send build data to webhook')
   }

   manager.listener.logger.println 'Successfully processed and sent build data'
} catch (Exception e) {
   manager.listener.logger.println "Error: ${e.message}"
   manager.listener.logger.println "Stack trace: ${e.stackTrace}"
   throw e
}

```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hivel.ai/integrations/ci-cd/jenkins-freestyle-integration-with-ansible-and-hivel-webhook.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
