Back to Blueprints

TestFlight to JIRA Integration - Cloudflare Worker

Updated 1/10/2026
vv1.0
blueprintvibe-codingwebhooksautomationjiratestflight

Works with AI coding agents

These blueprints are optimized for use with:

TestFlight to JIRA Integration - Cloudflare Worker

Overview

This blueprint walks you through building a Cloudflare Worker that automatically receives TestFlight feedback webhooks from App Store Connect and creates well-formatted JIRA tickets with all relevant information—including screenshots, device details, app version, and tester information.

It is designed for:

  • teams managing beta testing through TestFlight who want automated ticket creation
  • developers who need reliable webhook handling with proper error handling
  • anyone who wants to integrate TestFlight feedback into their existing JIRA workflow

The focus is on automation and reliability with proper error handling, retry logic, and JIRA's Atlassian Document Format (ADF) formatting.


What You'll Build

By the end of this blueprint, you'll have:

  • A Cloudflare Worker that receives TestFlight webhook notifications
  • Automatic JIRA ticket creation with formatted descriptions
  • Screenshot download and upload to JIRA (preventing expiration)
  • Device information, OS version, and app version/build number in descriptions
  • Automatic reporter assignment based on email-to-JIRA-user mapping
  • Optional AI-powered title summarization using OpenAI
  • Comprehensive error handling and logging
  • Support for both text feedback and screenshot submissions

How to Use This Blueprint

This blueprint is meant to be followed step by step using an AI coding agent such as Cursor.

Important rules:

  • Follow the sequence as written
  • Do not skip verification steps
  • Ignore agent suggestions that expand scope

The goal is to finish with something working, not perfect.


Pre-requisites

Accounts & Tools

Add Documentation as Agent Context (Strongly Recommended)

Keep these docs open or add them as context in your editor:


Prompt 0 — Agent Working Agreement

You are my senior full-stack engineer working inside this repo.

Rules:
- Keep scope minimal and shippable.
- Prefer boring defaults.
- Do not add dependencies unless necessary; ask first.
- Make small, reviewable changes.
- Explain which files you touched.
- Stop after completing each task.
- Follow the blueprint sequence. Do not jump ahead.
- Use TypeScript for type safety.
- All environment variables should be clearly named and documented.
- Use Cloudflare Workers runtime APIs (no Node.js-specific APIs).

Verification:

  • The agent acknowledges the rules
  • No scope expansion is proposed

Prompt 1 — Project Setup and Configuration (Agent)

Goal: Initialize the Cloudflare Worker project structure with TypeScript, dependencies, and basic configuration files.

Task: Set up a new Cloudflare Worker project for TestFlight to JIRA integration.

Requirements:
- Create a new directory for the project
- Initialize npm project with package.json
- Set up [TypeScript](https://www.typescriptlang.org) configuration (tsconfig.json)
- Create wrangler.toml with basic [Cloudflare Worker](https://developers.cloudflare.com/workers/) configuration
- Install required dependencies: @cloudflare/workers-types, typescript, wrangler
- Create src/ directory structure
- Set up scripts in package.json: dev, deploy, tail, type-check

Deliverables:
- package.json with proper dependencies and scripts
- tsconfig.json configured for Cloudflare Workers
- wrangler.toml with name, main, compatibility_date, and workers_dev = true
- src/ directory created
- .gitignore file (if not exists)

Stop after all files are created and npm install completes successfully.

Verification:

  • npm install runs without errors
  • npm run type-check passes
  • wrangler.toml exists with correct configuration
  • src/ directory exists

Prompt 2 — Type Definitions (Agent)

Goal: Create TypeScript interfaces for TestFlight webhooks, JIRA API, and environment variables.

Task: Create comprehensive TypeScript type definitions in src/types.ts.

Requirements:
- Define interfaces for [App Store Connect](https://developer.apple.com/documentation/appstoreconnectapi) webhook payloads (JSON:API format)
- Define interfaces for TestFlight feedback data structures
- Define interfaces for [JIRA](https://www.atlassian.com/software/jira) API requests and responses
- Define JIRA Atlassian Document Format (ADF) structures
- Define Env interface for [Cloudflare Worker](https://developers.cloudflare.com/workers/) environment variables
- Include all required and optional environment variables
- Add WorkerResponse interface for API responses

Environment variables to include:
- JIRA_DOMAIN (required)
- JIRA_EMAIL (required)
- JIRA_API_TOKEN (required)
- JIRA_PROJECT_KEY (required)
- JIRA_ISSUE_TYPE (required)
- APPSTORE_API_KEY (required)
- APPSTORE_KEY_ID (required)
- APPSTORE_ISSUER_ID (required)
- OPENAI_API_KEY (optional)
- TESTFLIGHT_WEBHOOK_SECRET (optional)
- JIRA_REPORTER_MAPPING (optional)
- JIRA_FALLBACK_REPORTER_ID (optional)

Deliverables:
- src/types.ts file with all type definitions
- Proper TypeScript interfaces for webhook payloads
- Proper TypeScript interfaces for JIRA API structures
- Env interface matching all environment variables

Stop after src/types.ts is created and type-check passes.

Verification:

  • src/types.ts exists with all required interfaces
  • npm run type-check passes without errors
  • All environment variables are typed in Env interface

Prompt 3 — Utility Functions (Agent)

Goal: Create utility functions for logging, JSON responses, environment validation, and retry logic.

Task: Create utility functions in src/utils.ts.

Requirements:
- Implement logEvent function for structured logging (INFO, ERROR, WARN levels)
- Implement jsonResponse helper for consistent JSON API responses
- Implement validateEnv function to check for required environment variables
- Implement retry logic helper function (optional, but recommended)
- All functions should be properly typed

Deliverables:
- src/utils.ts with logEvent, jsonResponse, and validateEnv functions
- Proper error handling and type safety

Stop after src/utils.ts is created and type-check passes.

Verification:

  • src/utils.ts exists with required functions
  • npm run type-check passes
  • Functions are properly typed and exported

Prompt 4 — JWT Generation for App Store Connect (Agent)

Goal: Implement JWT token generation for authenticating with App Store Connect API.

Task: Create JWT generation function in src/jwt.ts for [App Store Connect](https://developer.apple.com/documentation/appstoreconnectapi) API authentication.

Requirements:
- Implement function to generate JWT tokens using ES256 algorithm
- Use the private key from APPSTORE_API_KEY environment variable
- Include Key ID (APPSTORE_KEY_ID) and Issuer ID (APPSTORE_ISSUER_ID) in token
- Token should expire after reasonable time (e.g., 20 minutes)
- Use Web Crypto API available in [Cloudflare Workers](https://developers.cloudflare.com/workers/) runtime

Note: [Cloudflare Workers](https://developers.cloudflare.com/workers/) support Web Crypto API. You may need to use a library or implement ES256 signing manually. Check if @tsndr/cloudflare-worker-jwt or similar is available, or implement using Web Crypto.

Deliverables:
- src/jwt.ts with generateJWT function
- Proper error handling for JWT generation failures
- Function accepts env: Env and returns Promise<string>

Stop after src/jwt.ts is created and JWT generation works (you can test locally if needed).

Verification:

  • src/jwt.ts exists with generateJWT function
  • Function is properly typed
  • npm run type-check passes

Prompt 5 — App Store Connect API Client (Agent)

Goal: Create functions to fetch feedback data from App Store Connect API.

Task: Create [App Store Connect](https://developer.apple.com/documentation/appstoreconnectapi) API client in src/appstoreconnect.ts.

Requirements:
- Implement appStoreConnectAPIRequest function that adds JWT authentication
- Implement fetchScreenshotSubmission function to fetch screenshot submission details
- Implement fetchFeedbackFromAPI function to fetch text feedback details
- Handle JSON:API format responses
- Include build relationship when fetching screenshot submissions
- After fetching build, make a separate API call to fetch build with preReleaseVersion included
- Merge preReleaseVersion data into included array for later extraction
- Proper error handling and logging
- Support for extracting version and build number from relationships

Functions needed:
- appStoreConnectAPIRequest(url: string, env: Env): Promise<Response>
- fetchScreenshotSubmission(screenshotSubmissionId: string, apiUrl: string, env: Env): Promise<BetaFeedbackScreenshotSubmissionResponse>
- fetchFeedbackFromAPI(feedbackId: string, apiUrl: string, env: Env): Promise<BetaFeedbackResponse>
- Helper functions to check event types (isFeedbackSubmissionEvent, isScreenshotSubmissionEvent, etc.)

Deliverables:
- src/appstoreconnect.ts with all API client functions
- Proper TypeScript types for API responses
- Error handling and logging

Stop after src/appstoreconnect.ts is created and type-check passes.

Verification:

  • src/appstoreconnect.ts exists with required functions
  • Functions handle App Store Connect API authentication
  • Functions fetch build and preReleaseVersion relationships correctly
  • npm run type-check passes

Prompt 6 — TestFlight Payload Parsing (Agent)

Goal: Create functions to parse and transform App Store Connect webhook payloads.

Task: Create TestFlight payload parsing functions in src/testflight.ts.

Requirements:
- Implement parseAppStoreConnectPayload to parse JSON:API webhook format
- Implement convertAppStoreConnectPayload to transform to internal format
- Handle both betaFeedbackSubmissionCreated and betaFeedbackScreenshotSubmissionCreated event types
- Extract relationships and included data properly
- Validate payload structure

Deliverables:
- src/testflight.ts with parsing and transformation functions
- Proper error handling for malformed payloads
- Type-safe parsing

Stop after src/testflight.ts is created and type-check passes.

Verification:

  • src/testflight.ts exists with parsing functions
  • Functions handle JSON:API format correctly
  • npm run type-check passes

Prompt 7 — JIRA API Client and Issue Creation (Agent)

Goal: Create comprehensive JIRA API client with issue creation, attachment upload, and reporter assignment.

Task: Create [JIRA](https://www.atlassian.com/software/jira) API client in src/jira.ts with issue creation, formatting, and reporter management.

Requirements:
- Implement createJIRAIssue function with retry logic
- Implement formatJIRADescription functions for both text and screenshot feedback
- Extract version and build number from included data and format as "vX.Y Build Z"
- Implement createJIRAPayload function that includes labels: ["TestFlight"]
- Implement updateJIRAIssueReporter function to set reporter after issue creation
- Implement loadReporterMapping function to load mapping from jira-reporter-mapping.json or JIRA_REPORTER_MAPPING env var
- Implement resolveReporterFromEmail function with case-insensitive email matching
- Implement uploadScreenshotsToJIRA function to download and upload screenshots
- Use [JIRA REST API v3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/) with proper authentication (Basic Auth with API token)
- Format descriptions using Atlassian Document Format (ADF)
- Include device info, OS version, tester email, app version/build in descriptions
- Prefix issue titles with "[TestFlight Feedback]"
- Handle multiple screenshot attachments

Key functions:
- createJIRAIssue(payload: JIRACreateIssuePayload, env: Env, maxRetries?: number): Promise<JIRACreateIssueResponse>
- createJIRAIssueFromFeedback(feedback: BetaFeedback, env: Env): Promise<JIRACreateIssueResponse>
- createJIRAIssueFromScreenshotSubmission(screenshotSubmission: BetaFeedbackScreenshotSubmission, env: Env): Promise<JIRACreateIssueResponse>
- formatJIRADescriptionFromScreenshotSubmission(screenshotSubmission: BetaFeedbackScreenshotSubmission, includedData?: unknown[]): JIRADescription
- updateJIRAIssueReporter(issueKey: string, reporterId: string, env: Env): Promise<void>
- loadReporterMapping(env: Env): ReporterMapping
- resolveReporterFromEmail(email: string | undefined, env: Env): string | null
- uploadScreenshotsToJIRA(issueKey: string, screenshots: Array<{url: string, width: number, height: number}>, env: Env): Promise<void>

Deliverables:
- src/jira.ts with all JIRA API functions
- Proper error handling and retry logic
- Reporter mapping support with fallback
- Version/build number extraction and formatting

Stop after src/jira.ts is created and type-check passes.

Verification:

  • src/jira.ts exists with all required functions
  • Functions use JIRA REST API v3 correctly
  • Reporter mapping logic is implemented
  • Version/build formatting is correct
  • npm run type-check passes

Prompt 8 — OpenAI Integration (Optional) (Agent)

Goal: Create optional OpenAI integration for AI-powered title summarization.

Task: Create [OpenAI](https://platform.openai.com) API client in src/openai.ts for summarizing feedback text into concise titles.

Requirements:
- Implement summarizeFeedback function that calls [OpenAI API](https://platform.openai.com)
- Use GPT-4o-mini model (or similar cost-effective model)
- Only call OpenAI if OPENAI_API_KEY is set
- Return original text truncated if OpenAI is not available or fails
- Limit title length to reasonable size (e.g., 100 characters)
- Handle API errors gracefully

Deliverables:
- src/openai.ts with summarizeFeedback function
- Proper error handling
- Fallback to simple truncation if OpenAI unavailable

Stop after src/openai.ts is created and type-check passes.

Verification:

  • src/openai.ts exists (if implementing OpenAI feature)
  • Function handles missing API key gracefully
  • npm run type-check passes

Prompt 9 — Main Worker Handler (Agent)

Goal: Create the main Cloudflare Worker entry point that handles webhook requests.

Task: Create main worker handler in src/index.ts that orchestrates the entire flow.

Requirements:
- Handle POST requests only (return 405 for other methods)
- Validate Content-Type is application/json
- Validate environment variables
- Parse webhook payload
- Determine event type (feedback vs screenshot submission)
- Fetch full feedback data from [App Store Connect](https://developer.apple.com/documentation/appstoreconnectapi) API
- Create [JIRA](https://www.atlassian.com/software/jira) issue with proper formatting
- Upload screenshots if present
- Return appropriate JSON responses
- Handle CORS preflight requests
- Comprehensive error handling and logging

Flow:
1. Validate request method and content type
2. Validate environment variables
3. Parse webhook payload
4. Check event type
5. Fetch full data from App Store Connect API
6. Create JIRA issue
7. Update reporter if mapping exists
8. Upload screenshots if present
9. Return success response with JIRA issue details

Deliverables:
- src/index.ts with complete worker handler
- Proper error handling for all failure cases
- Structured logging throughout
- Success/error JSON responses

Stop after src/index.ts is created and type-check passes.

Verification:

  • src/index.ts exists with complete handler
  • All error cases are handled
  • npm run type-check passes
  • Handler follows the flow described above

Prompt 10 — Reporter Mapping Configuration File (Agent)

Goal: Create the JSON mapping file for email-to-JIRA-user-ID mapping.

Task: Create jira-reporter-mapping.json file in the repository root.

Requirements:
- Create JSON file with mappings object and fallbackReporterId
- Structure: {"mappings": {}, "fallbackReporterId": null}
- File should be committed to repository
- Document the format in comments or README

Deliverables:
- jira-reporter-mapping.json file with proper structure
- Empty mappings object ready for user to populate

Stop after jira-reporter-mapping.json is created.

Verification:

  • jira-reporter-mapping.json exists with correct structure
  • File is valid JSON

Prompt 11 — Environment Variables Setup (Manual)

Goal: Set up all required Cloudflare Worker secrets.

Task: Configure all environment variables/secrets in Cloudflare Workers.

Manual Steps:
1. Login to [Cloudflare](https://cloudflare.com): npx wrangler login
2. Set [JIRA](https://www.atlassian.com/software/jira) secrets:
   - npx wrangler secret put JIRA_DOMAIN
   - npx wrangler secret put JIRA_EMAIL
   - npx wrangler secret put JIRA_API_TOKEN
   - npx wrangler secret put JIRA_PROJECT_KEY
   - npx wrangler secret put JIRA_ISSUE_TYPE

3. Set [App Store Connect](https://appstoreconnect.apple.com) secrets:
   - npx wrangler secret put APPSTORE_API_KEY (paste entire .p8 file contents)
   - npx wrangler secret put APPSTORE_KEY_ID
   - npx wrangler secret put APPSTORE_ISSUER_ID

4. Optional secrets:
   - npx wrangler secret put OPENAI_API_KEY (if using AI summarization)
   - npx wrangler secret put JIRA_FALLBACK_REPORTER_ID (if using reporter mapping)
   - npx wrangler secret put JIRA_REPORTER_MAPPING (JSON override, optional)

Instructions:
- Get [JIRA API token](https://id.atlassian.com/manage-profile/security/api-tokens) from Atlassian account settings
- Get [App Store Connect API credentials](https://appstoreconnect.apple.com/access/api) from App Store Connect
- Each command will prompt for the secret value (input is hidden)

Stop after all required secrets are set.

Verification:

  • All required secrets are set via npx wrangler secret list
  • JIRA API token is valid and not expired
  • App Store Connect API credentials are correct

Prompt 12 — Configure Reporter Mapping (Manual)

Goal: Set up email-to-JIRA-user-ID mapping for automatic reporter assignment.

Task: Configure reporter mapping in jira-reporter-mapping.json.

Manual Steps:
1. Find [JIRA](https://www.atlassian.com/software/jira) user account IDs:
   - Go to JIRA Settings > User management > People
   - Click on each user to view profile
   - Extract account ID from URL (format: 712020:31c89ce5-8ffc-4ca2-9e1a-a68c84acc24c)

2. Edit jira-reporter-mapping.json:
   - Add email-to-account-ID mappings
   - Set fallbackReporterId to a default user ID
   - Save the file

3. Ensure your [JIRA](https://www.atlassian.com/software/jira) API token user has "Modify Reporter" permission

Example structure:
{
  "mappings": {
    "tester@example.com": "712020:31c89ce5-8ffc-4ca2-9e1a-a68c84acc24c",
    "another@example.com": "712020:another-user-id"
  },
  "fallbackReporterId": "712020:fallback-user-id"
}

Stop after jira-reporter-mapping.json is configured with your mappings.

Verification:

  • jira-reporter-mapping.json contains email mappings
  • Fallback reporter ID is set
  • All account IDs are valid JIRA user IDs

Prompt 13 — Deploy Worker (Manual)

Goal: Deploy the Cloudflare Worker to production.

Task: Deploy the worker to Cloudflare.

Manual Steps:
1. Run: npm run deploy
2. Note the [Cloudflare Worker](https://developers.cloudflare.com/workers/) URL (e.g., https://testflight-jira.your-subdomain.workers.dev)
3. Verify deployment succeeded
4. Test the endpoint is accessible (should return method not allowed for GET)

Stop after worker is deployed and URL is noted.

Verification:

  • npm run deploy completes successfully
  • Worker URL is accessible
  • Worker returns appropriate error for GET requests

Prompt 14 — Configure App Store Connect Webhook (Manual)

Goal: Set up webhook in App Store Connect to send notifications to the worker.

Task: Configure webhook in App Store Connect.

Manual Steps:
1. Log in to [App Store Connect](https://appstoreconnect.apple.com)
2. Navigate to Users and Access > Integrations > Webhooks
3. Click "Create Webhook"
4. Configure:
   - URL: Your [Cloudflare Worker](https://developers.cloudflare.com/workers/) URL (e.g., https://testflight-jira.your-subdomain.workers.dev)
   - Event: Select "TestFlight feedback submission" (or similar)
   - Secret Key: (Optional) Set if you want signature verification
5. Save the webhook
6. Test by submitting feedback through TestFlight

Stop after webhook is created and enabled in App Store Connect.

Verification:

  • Webhook is created in App Store Connect
  • Webhook URL points to your Cloudflare Worker
  • Webhook is enabled

Prompt 15 — Test End-to-End Flow (Manual)

Goal: Verify the entire integration works correctly.

Task: Test the complete flow from TestFlight feedback to JIRA ticket creation.

Manual Steps:
1. Submit test feedback through TestFlight (with screenshot if possible)
2. Monitor [Cloudflare Worker](https://developers.cloudflare.com/workers/) logs: npx wrangler tail
3. Check [JIRA](https://www.atlassian.com/software/jira) project for newly created ticket
4. Verify:
   - Ticket title is prefixed with "[TestFlight Feedback]"
   - Description includes device info, OS version, app version/build
   - Screenshots are attached (if submitted)
   - Reporter is set correctly (if mapping configured)
   - Labels include "TestFlight"

5. If errors occur, check logs and fix issues

Stop after successful test feedback creates a JIRA ticket with all expected information.

Verification:

  • Test feedback submission triggers webhook
  • JIRA ticket is created successfully
  • Ticket includes all expected information
  • Screenshots are attached (if submitted)
  • Reporter is set correctly (if configured)
  • Version/build number displays correctly (e.g., "v1.3 Build 2")

Optional Prompts (Nice-to-have)

Prompt 16 — Add Webhook Signature Verification (Optional)

Task: Implement webhook signature verification if TESTFLIGHT_WEBHOOK_SECRET is set.

Requirements:
- Verify X-Apple-Webhook-Signature header if secret is configured
- Return 401 if signature verification fails
- Use HMAC-SHA256 for verification
- Only verify if secret is set (optional feature)

Deliverables:
- Signature verification function in src/utils.ts or src/index.ts
- Proper error handling

Stop after signature verification is implemented and tested.

Prompt 17 — Add Custom Fields Support (Optional)

Task: Add support for custom JIRA fields via environment variables.

Requirements:
- Support JIRA_CUSTOMFIELD_* environment variables
- Allow mapping TestFlight data to custom fields
- Document custom field usage

Deliverables:
- Custom field mapping logic in src/jira.ts
- Documentation update

Stop after custom fields support is added.

Known Pitfalls

  • JIRA API Token Expiration: Tokens expire after 366 days. Set a reminder to renew before expiration.
  • Nested Include Syntax: App Store Connect API doesn't support build.preReleaseVersion nested includes. Must fetch build separately.
  • Reporter Field Permissions: JIRA API token user needs "Modify Reporter" permission for reporter assignment to work.
  • Screen Configuration: Reporter field may not be available on Create screen, but can be updated after creation.
  • Email Case Sensitivity: Always normalize emails to lowercase for mapping.
  • Screenshot Expiration: Screenshots from TestFlight expire, so download and upload to JIRA immediately.
  • Rate Limiting: JIRA API has rate limits (typically 100 requests/minute). Retry logic handles this.

Definition of Done

  • Worker receives webhook from App Store Connect
  • JIRA tickets are created with proper formatting
  • Screenshots are downloaded and uploaded to JIRA
  • Version and build number display correctly (e.g., "v1.3 Build 2")
  • Reporter mapping works (if configured)
  • Error handling works for all failure cases
  • Logging provides useful debugging information
  • All environment variables are documented
  • Blueprint can be followed start-to-finish without guessing

Changelog

  • v1.0 — Initial release
    • Complete TestFlight to JIRA integration
    • Screenshot attachment support
    • Reporter mapping with fallback
    • Version/build number extraction
    • Optional AI-powered title summarization