AWS Builder Center: Learn, Build and Connect with builders in the AWS community
AWS Builder Center is the official home for builders on AWS. Share and read what others are working on, follow people who inspire you, explore training and workshops, and find tools to support what you're building.
Bidirectional Jira ↔ AWS DevOps Agent Integration
AWS DevOps Agent can investigate incidents autonomously, but it doesn't have a native Jira integration. This article shows how to connect the two bidirectionally
Authors: Manish Mishra (Sr TAM) & Ayush Bhambhani (TAM)
Introduction
AWS DevOps Agent can investigate incidents autonomously, but it doesn't have a native Jira integration. This article shows how to connect the two bidirectionally:
- Jira → DevOps Agent: A new Jira ticket triggers an investigation via webhook
- DevOps Agent → Jira: Investigation results are posted back as a comment on the ticket
The entire AWS-side infrastructure deploys with a single CloudFormation command — three parameters, no intermediate services.
The full bidirectional flow
Jira AWS Account
──── ───────────
1. Jira ticket created ── webhook ──► DevOps Agent Webhook
(automation triggers (API Key) (generic endpoint)
"Send web request") │
▼
DevOps Agent
(investigates autonomously)
│
▼
2. Comment posted ◄── Jira API ── Callback Lambda
with RCA findings ▲
│ triggered by
EventBridge
(Investigation Completed /
Linked / Failed / Cancelled)
What you get
A CloudFormation stack that creates:
| Resource | What it does |
|---|---|
| Secrets Manager | Stores Jira base URL, email, and API token |
| IAM Role | Read-only access to DevOps Agent API + specific secret |
| Lambda Function | Fetches investigation summary, handles linked tickets, posts to Jira |
| EventBridge Rule | Catches all 6 investigation lifecycle events |
| Lambda Permission | Allows EventBridge to invoke the Lambda |
Prerequisites
- An AWS DevOps Agent Space with an API Key webhook configured
- A Jira Cloud API token (create one here)
- The Jira bot account needs Add Comments permission on your project(s)
- AWS CLI configured with permissions to deploy CloudFormation stacks
⚠️ The DevOps Agent webhook uses API Key authentication (not HMAC).
Deploy in one command
aws cloudformation deploy \ --template-file cfn-jira-devops-agent-callback.yaml \ --stack-name devops-agent-jira-callback \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides \ JiraBaseUrl="https://your-domain.atlassian.net" \ JiraEmail="devops-bot@yourcompany.com" \ JiraApiToken="your-jira-api-token" \ --region us-east-1
Three parameters, one command — that deploys the full callback infrastructure. The API token is stored in Secrets Manager (NoEcho — won't appear in CloudFormation output). No Agent Space ID needed — the Lambda reads it from each event dynamically.
After deployment: Configure Jira
The CloudFormation stack handles the callback side (DevOps Agent → Jira). You still need to configure the trigger side (Jira → DevOps Agent) in your Jira project.
Create the Jira automation rule
- Go to Settings → System → Global automation → Create flow/rule
- Choose Create from scratch
- Trigger: Work item created
- Action: Send web request
URL:
https://event-ai.<region>.api.aws/webhook/generic/<your-webhook-id>
Method: POST
Headers:
| Name | Value |
|---|---|
Content-Type | application/json |
Authorization | Bearer <your-devops-agent-api-key> |
Body (Custom data):
{ "eventType": "incident", "incidentId": "{{issue.key}}-{{now.toEpochMillis()}}", "action": "created", "priority": "{{issue.priority.name.toUpperCase()}}", "title": "[Jira {{issue.key}}] {{issue.summary}}", "description": "{{issue.description}}", "timestamp": "{{now.format(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\")}}", "service": "{{issue.components.first.name}}", "data": { "metadata": { "jira_issue_key": "{{issue.key}}", "project": "{{issue.project.key}}", "reporter": "{{issue.reporter.displayName}}" } } }
- Name the rule and enable it.
Priority must be uppercase (
HIGH,CRITICAL, notHigh). Use.toUpperCase().
Epoch in incidentId prevents DevOps Agent from deduplicating repeated triggers on the same ticket.
How the Lambda handles each event
| Event | What gets posted to Jira |
|---|---|
| Completed | Full RCA summary on the original ticket + all linked tickets |
| Linked | "Linked to original investigation from: [ORIGINAL-KEY]" + reason |
| Failed | "Could not complete - investigate manually" + reason |
| Timed Out | "Timed out" + reason |
| Cancelled | "Cancelled" + reason (e.g., USER_CANCELED) |
| Skipped | "Skipped based on criteria" + reason |
Linked ticket handling
When DevOps Agent links a new incident to a previous investigation:
- The linked ticket's comment includes which original ticket it's linked to
- When the primary investigation completes, all linked tickets get the findings too
End-to-end
- Create a Jira ticket with High priority
- Check Jira automation audit log for HTTP 200
- Wait for investigation to complete
- Verify a comment appears on the ticket with the findings
Cleanup
aws cloudformation delete-stack \ --stack-name devops-agent-jira-callback \ --region us-east-1
This removes the Lambda, IAM role, EventBridge rule, secret, and all permissions.
Optional: Kiro automated setup
If you use Kiro, download both files:
cfn-jira-devops-agent-callback.yaml— place in your workspace root- Save the steering file below as
.kiro/steering/jira-devops-agent-integration.md
Then tell Kiro: "Install the Jira DevOps Agent integration". It will collect your credentials, deploy the stack, and walk you through the Jira configuration.
Steering file
# Jira ↔ DevOps Agent Bidirectional Integration ## When to use Use this steering when the user asks to: - Install Jira DevOps Agent integration - Set up Jira callback for DevOps Agent - Connect Jira to DevOps Agent - Deploy the Jira investigation integration - Set up bidirectional Jira DevOps Agent ## What this does Deploys a CloudFormation stack that: 1. Creates a Lambda that posts DevOps Agent investigation results back to Jira tickets 2. Creates an EventBridge rule that catches all investigation lifecycle events 3. Stores Jira credentials in Secrets Manager Then guides the user through configuring the Jira automation rule (trigger side). ## Prerequisites Before starting, confirm the user has: - [ ] Both files in their workspace: - `.kiro/steering/jira-devops-agent-integration.md` (this file) - `cfn-jira-devops-agent-callback.yaml` (CloudFormation template) - [ ] A DevOps Agent Space with an **API Key** webhook (not HMAC) - [ ] Their webhook URL and API key - [ ] A Jira Cloud API token - [ ] The Jira bot account email - [ ] Their Jira base URL (e.g., `https://org.atlassian.net`) ## Steps ### Step 1: Collect credentials Ask the user for: 1. **Jira base URL** — e.g., `https://yourorg.atlassian.net` 2. **Jira email** — the account that will post comments 3. **Jira API token** — from https://id.atlassian.com/manage-profile/security/api-tokens ### Step 2: Deploy the CloudFormation stack Run: `aws cloudformation deploy \ --template-file cfn-jira-devops-agent-callback.yaml \ --stack-name devops-agent-jira-callback \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides \ JiraBaseUrl="<user-provided>" \ JiraEmail="<user-provided>" \ JiraApiToken="<user-provided>" \ --region us-east-1 ` Wait for completion and verify: aws cloudformation describe-stacks \ --stack-name devops-agent-jira-callback \ --query 'Stacks[0].StackStatus' \ --region us-east-1 Expected: CREATE_COMPLETE ### Step 3: Guide Jira automation setup Tell the user to create a Jira automation rule: 1. **Settings** → **Automation** → **Global automation** → **Create flow** 2. Choose **Create from scratch** 3. Trigger: **Work item created** 4. Action: **Send web request** 5. Configure: - URL: https://event-ai.us-east-1.api.aws/webhook/generic/<their-webhook-id> - Method: POST - Headers: Content-Type: application/json and Authorization: Bearer <their-api-key> - Body: Use the webhook payload from the main article 6. Name: "Trigger DevOps Agent Investigation" 7. Enable the rule ### Step 4: Test Ask the user to create a test Jira ticket with High priority, then verify a task was created in DevOps Agent and a comment appears on the ticket after investigation completes. ### Step 5: Confirm - The integration is live - All investigation events will post comments to Jira - Linked tickets will show which original ticket they're linked to - To remove: aws cloudformation delete-stack --stack-name devops-agent-jira-callback --region us-east-1 ## Important notes - Webhook must be **API Key** type. HMAC will not work. - Priority must be **uppercase** (HIGH, CRITICAL, not High). - The Lambda is space-independent — no agent space ID needed.
The complete CloudFormation template
Save as cfn-jira-devops-agent-callback.yaml and deploy with the command at the top of this article.
AWSTemplateFormatVersion: '2010-09-09' Description: > DevOps Agent Jira Callback - Automatically posts investigation results back to Jira tickets as comments. Handles all investigation lifecycle events (Completed, Linked, Failed, Cancelled, Timed Out, Skipped). Parameters: JiraBaseUrl: Type: String Description: Jira Cloud base URL (e.g., https://yourorg.atlassian.net) AllowedPattern: ^https://.+$ JiraEmail: Type: String Description: Email for the Jira bot account that posts comments JiraApiToken: Type: String NoEcho: true Description: Jira API token (from id.atlassian.com/manage-profile/security/api-tokens) Resources: # --- Secrets Manager --- JiraSecret: Type: AWS::SecretsManager::Secret Properties: Name: devops-agent-jira-callback Description: Jira API credentials for DevOps Agent callback SecretString: !Sub | { "jiraBaseUrl": "${JiraBaseUrl}", "email": "${JiraEmail}", "apiToken": "${JiraApiToken}" } # --- IAM Role --- CallbackLambdaRole: Type: AWS::IAM::Role Properties: RoleName: devops-agent-jira-callback-role AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: devops-agent-jira-access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - aidevops:ListJournalRecords - aidevops:GetBacklogTask - aidevops:ListBacklogTasks Resource: !Sub arn:aws:aidevops:${AWS::Region}:${AWS::AccountId}:agentspace/* - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:devops-agent-jira-callback-* ``` ```yaml # --- Lambda Function --- CallbackLambda: Type: AWS::Lambda::Function Properties: FunctionName: devops-agent-jira-callback Runtime: python3.12 Handler: index.lambda_handler Timeout: 60 MemorySize: 128 Role: !GetAtt CallbackLambdaRole.Arn Environment: Variables: REGION: !Ref AWS::Region JIRA_SECRET_ID: devops-agent-jira-callback Code: ZipFile: | import json import os import re import base64 import boto3 from urllib import request, error secrets_client = boto3.client('secretsmanager') devops_client = boto3.client('devops-agent', region_name=os.environ['REGION']) _jira_config = None def get_jira_config(): global _jira_config if _jira_config is None: resp = secrets_client.get_secret_value(SecretId=os.environ['JIRA_SECRET_ID']) _jira_config = json.loads(resp['SecretString']) return _jira_config def get_summary(agent_space_id, execution_id): for record_type in ['investigation_summary_md', 'investigation_result']: try: resp = devops_client.list_journal_records( agentSpaceId=agent_space_id, executionId=execution_id, recordType=record_type) records = resp.get('records', []) if records: content = records[0].get('content', '') try: parsed = json.loads(content) return parsed.get('text', content) except (json.JSONDecodeError, TypeError): return content except Exception as e: print(f"Error fetching {record_type}: {e}") return None def extract_jira_key(reference_id): if not reference_id: return '' m = re.match(r'^([A-Z]+-\d+)', reference_id) return m.group(1) if m else reference_id def build_comment(event_type, summary, linked_from=None, reason=None): reason_line = f"\nReason: {reason}" if reason else "" if event_type == 'Investigation Completed': heading = "AWS DevOps Agent - Investigation Completed" body_text = summary or "Investigation completed but no summary available." elif event_type == 'Investigation Linked': heading = "AWS DevOps Agent - Investigation Linked" prefix = "This incident is similar to a previously investigated issue." if linked_from: prefix += f"\nLinked to original investigation from: {linked_from}" if reason: prefix += reason_line body_text = f"{prefix}\n\n{summary}" if summary else prefix elif event_type == 'Investigation Failed': heading = "AWS DevOps Agent - Investigation Failed" body_text = f"The automated investigation could not complete. Please investigate manually.{reason_line}" elif event_type == 'Investigation Timed Out': heading = "AWS DevOps Agent - Investigation Timed Out" body_text = f"The automated investigation timed out. Please investigate manually.{reason_line}" elif event_type == 'Investigation Cancelled': heading = "AWS DevOps Agent - Investigation Cancelled" body_text = f"The automated investigation was cancelled.{reason_line}" elif event_type == 'Investigation Skipped': heading = "AWS DevOps Agent - Investigation Skipped" body_text = f"The investigation was skipped based on agent space criteria.{reason_line}" else: heading = f"AWS DevOps Agent - {event_type}" body_text = summary or f"Event: {event_type}{reason_line}" return heading, body_text def post_jira_comment(issue_key, heading, body_text): config = get_jira_config() base_url = config['jiraBaseUrl'].rstrip('/') url = f"{base_url}/rest/api/3/issue/{issue_key}/comment" body = {"body":{"version":1,"type":"doc","content":[ {"type":"heading","attrs":{"level":3},"content":[{"type":"text","text":heading}]}, {"type":"codeBlock","attrs":{"language":"markdown"},"content":[{"type":"text","text":body_text}]} ]}} auth = base64.b64encode(f"{config['email']}:{config['apiToken']}".encode()).decode() data = json.dumps(body).encode('utf-8') req = request.Request(url, data=data, headers={ 'Content-Type': 'application/json', 'Authorization': f'Basic {auth}'}) try: with request.urlopen(req, timeout=15) as resp: print(f"Jira comment posted: {resp.status} on {issue_key}") return resp.status except error.URLError as e: print(f"Jira POST failed for {issue_key}: {e}") return None def lambda_handler(event, context): print(f"Received: {json.dumps(event)}") event_type = event.get('detail-type', 'Unknown') metadata = event['detail']['metadata'] task_id = metadata['task_id'] execution_id = metadata.get('execution_id', '') agent_space_id = metadata['agent_space_id'] primary_key = '' status_reason = '' try: resp = devops_client.get_backlog_task(agentSpaceId=agent_space_id, taskId=task_id) task = resp.get('task', {}) ref = task.get('reference', {}) primary_key = extract_jira_key(ref.get('referenceId', '')) status_reason = task.get('statusReason', '') if not status_reason: meta = task.get('metadata', {}) status_reason = meta.get('canceledReason', '') or meta.get('failedReason', '') or meta.get('skippedReason', '') if task.get('primaryTaskId'): try: pr = devops_client.get_backlog_task(agentSpaceId=agent_space_id, taskId=task['primaryTaskId']) original_key = extract_jira_key(pr['task']['reference'].get('referenceId', '')) heading, body_text = build_comment(event_type, None, linked_from=original_key, reason=status_reason) result = post_jira_comment(primary_key, heading, body_text) return {'statusCode': 200, 'event_type': event_type, 'this_ticket': primary_key, 'linked_from': original_key} except Exception as e: print(f"Error getting primary task: {e}") except Exception as e: print(f"Error getting task: {e}") if not primary_key: return {'statusCode': 200, 'message': 'No issue key, skipped'} linked_tasks_info = {} try: all_tasks = devops_client.list_backlog_tasks(agentSpaceId=agent_space_id) for t in all_tasks.get('tasks', []): if t.get('primaryTaskId') == task_id: key = extract_jira_key(t.get('reference', {}).get('referenceId', '')) if key: linked_tasks_info[key] = t.get('statusReason', '') except Exception as e: print(f"Error listing linked tasks: {e}") summary = None if event_type in ['Investigation Completed', 'Investigation Linked']: summary = get_summary(agent_space_id, execution_id) results = {} heading, body_text = build_comment(event_type, summary, reason=status_reason) results[primary_key] = post_jira_comment(primary_key, heading, body_text) for linked_key, linked_reason in linked_tasks_info.items(): heading, body_text = build_comment(event_type, summary, linked_from=primary_key, reason=linked_reason) results[linked_key] = post_jira_comment(linked_key, heading, body_text) return {'statusCode': 200, 'event_type': event_type, 'primary_ticket': primary_key, 'linked_tickets': list(linked_tasks_info.keys())} # --- EventBridge Rule --- InvestigationEventRule: Type: AWS::Events::Rule Properties: Name: devops-agent-jira-callback Description: Triggers Jira callback on all DevOps Agent investigation lifecycle events State: ENABLED EventPattern: source: - aws.aidevops detail-type: - Investigation Completed - Investigation Linked - Investigation Failed - Investigation Timed Out - Investigation Cancelled - Investigation Skipped Targets: - Id: JiraCallbackLambda Arn: !GetAtt CallbackLambda.Arn # --- Lambda Permission for EventBridge --- LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref CallbackLambda Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt InvestigationEventRule.Arn Outputs: LambdaFunctionArn: Description: Callback Lambda ARN Value: !GetAtt CallbackLambda.Arn EventBridgeRuleArn: Description: EventBridge Rule ARN Value: !GetAtt InvestigationEventRule.Arn SecretArn: Description: Jira credentials secret ARN Value: !Ref JiraSecret
Conclusion
This integration connects Jira and DevOps Agent in both directions. A ticket triggers an investigation, and the results come back as a comment — covering completions, linked incidents, failures, and cancellations.
The approach uses API Key auth so Jira can call DevOps Agent directly without a signing proxy. The Lambda is space-independent, handles linked ticket fan-out, and includes the reason for every non-completion event.
The same pattern works with other ticketing systems like Opsgenie with the appropriate API call.
- Tags
- AWS DevOps Agent
- Language
- English
Relevant content
- Accepted Answer
asked 3 months ago
- Accepted Answer
asked 2 months ago
- Accepted Answer
asked 2 months ago
