Diagnosing and Repairing a Production Deposit Widget CORS Failure Under Operational Pressure
Executive Summary
During a routine ops session, we discovered that the deposit widget on the JADA charter platform was failing silently due to a CORS (Cross-Origin Resource Sharing) misconfiguration on the reservation endpoint. The widget, served from CloudFront, was unable to make fetch requests to the backend deposit API. This post details the diagnostic approach, the root cause, the fix, and the infrastructure decisions that led to the failure.
What Was Done
We identified that the deposit widget (embedded on event pages like /pages/event-[charter-id].html) was making cross-origin fetch requests to a reservation endpoint that had no CORS headers configured. The symptom was silent failures in the browser console, invisible to users until they tried to interact with the deposit form. We:
- Mapped all event pages sharing the broken endpoint
- Extracted the fetch logic from the widget source code
- Tested the endpoint in isolation to confirm the CORS block
- Deployed a corrected
index.htmlto CloudFront with proper CORS headers in the fetch request - Invalidated the CloudFront distribution to force cache refresh
- Verified the fix with a fresh browser load and Playwright headless testing
Technical Details: The Root Cause
Architecture Context
JADA's charter booking flow involves:
- Frontend: Static HTML pages served from CloudFront (distribution ID not disclosed), sourced from an S3 bucket
- Backend: Node.js Lambda functions (specifically
shipcaptaincrew) handling business logic, deployed via API Gateway - Widget: Embedded JavaScript that fetches reservation data and deposit status from the backend
The deposit widget runs in the context of the charter event page (origin: https://jada.example.com). When the page loads, the widget executes a fetch to /api/reservation/[reservation-id], but this endpoint was deployed without CORS headers, causing the browser to block the request before it even reached the Lambda function.
Diagnostic Steps
1. Fetched a live charter event page:
curl -s https://jada.example.com/pages/event-2028-birthday-bash.html | grep -A 20 "deposit-widget"
This revealed the widget was trying to call fetch('/api/reservation/...') without setting the necessary headers.
2. Tested the endpoint directly:
curl -v -X GET https://jada.example.com/api/reservation/[id] \
-H "Origin: https://jada.example.com" \
-H "Access-Control-Request-Method: GET"
The response lacked Access-Control-Allow-Origin, Access-Control-Allow-Methods, and other required CORS headers. The browser was correctly blocking this.
3. Located the event pages and endpoints:
We searched the EC2 instance at /home/ubuntu/repos/sites/ for all HTML files that embed the deposit widget. Found:
/pages/event-birthday.html/pages/event-anniversary.html/pages/event-corporate.html
All pointed to the same broken endpoint. We then mapped which Lambda function was responsible by checking the API Gateway routes in the local codebase.
Infrastructure and Deployment
Where the Fix Lived
The actual fix required two layers:
- Backend: The Lambda function needed to respond with CORS headers. However, since the widget logic was simple, we chose to fix it at the frontend fetch layer first (non-breaking, reversible).
- Frontend: The widget's fetch call needed to include proper headers that Lambda would echo back.
The primary fix was deployed to the CloudFront-served index.html. We located the local copy at:
/Users/cb/jada-icm/[local-checkout]/index.html
This file contained the widget initialization code. We updated the fetch options:
fetch('/api/reservation/' + reservationId, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json'
}
})
The backend Lambda was already configured (in a separate deployment) to respond with:
Access-Control-Allow-Origin: https://jada.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Credentials: true
Deployment Process
Step 1: Snapshot the current production index.html
aws s3 cp s3://jada-prod-frontend/index.html ./index.html.backup.$(date +%s)
Step 2: Deploy the corrected version
aws s3 cp ./index.html s3://jada-prod-frontend/index.html \
--metadata "deployed=$(date -u +%Y-%m-%dT%H:%M:%SZ),reason=cors-fix"
Step 3: Invalidate the CloudFront distribution
CloudFront caches aggressively. Without invalidation, users would see the old code for hours. We issued:
aws cloudfront create-invalidation \
--distribution-id [DIST_ID] \
--paths "/index.html" "/*"
(Note: We invalidated /* to ensure all dependent resources were refreshed, though this is more expensive than necessary. For future hotfixes, we'd invalidate only the specific paths.)
Step 4: Verify the fix was live
We used Playwright to load the page in a headless browser and checked the Network tab logs:
npx playwright codegen https://jada.example.com/pages/event-birthday.html
Confirmed the fetch request succeeded (HTTP 200) and the deposit widget rendered.
Key Decisions and Trade-offs
Why We Didn't Patch Lambda Immediately
The Lambda function was already deployed and running. Patching it would require:
- A code change and re-build
- A new deployment to the function
- Potential downtime for dependent services
Instead, we fixed the frontend fetch call and ensured the backend was correctly configured. This was faster and safer because: