Automating Charter Bookings Across Multiple Systems: Event Synchronization, Authentication, and CloudFront Header Stripping
When a new charter is booked through Boatsetter, we needed to synchronize that booking across three separate internal systems: the JADA Internal Calendar (for scheduling), ShipCaptainCrew (SCC, for crew notifications and task management), and a custom guest-facing booking confirmation page. This post documents the technical challenges we encountered—particularly around API authentication and CloudFront header stripping—and the patterns we used to solve them.
The Problem: Multi-System Synchronization
A single booking event needed to trigger:
- A calendar entry in JADA Internal Calendar with correct auth tokens
- An SCC event that auto-notifies crew members via magic links
- A static guest-facing HTML page uploaded to S3 and served through CloudFront
- Email confirmation sent to crew asking for availability
The challenge wasn't the individual integrations—each system had documented APIs—but rather the authentication mechanisms and infrastructure constraints that made straightforward API calls fail silently.
Authentication Challenges: Dashboard Lambda and Service Key Hashing
The JADA dashboard Lambda that coordinates these operations sits behind API Gateway and CloudFront. It requires an X-Dashboard-Token header for all requests. Finding and validating this token required tracing the entire request flow.
Token Discovery and Validation:
# Located in: /var/task/update_dashboard.py (Lambda source)
# Token passed through environment: DASHBOARD_TOKEN
# Used in calendar Lambda calls:
headers = {
'X-Dashboard-Token': os.environ['DASHBOARD_TOKEN'],
'Content-Type': 'application/json'
}
response = requests.post(calendar_lambda_url, json=payload, headers=headers)
The SCC system, however, uses a service key authentication pattern. The Lambda environment contains SERVICE_KEY_HASH, which is compared against a SHA-256 hash of the incoming service key:
# Located in: SCC Lambda (retrieved and analyzed)
# Auth mechanism in handler:
import hashlib
service_key = event.get('headers', {}).get('X-Service-Key', '')
service_key_hash = hashlib.sha256(service_key.encode()).hexdigest()
if service_key_hash != os.environ.get('SERVICE_KEY_HASH'):
return {'statusCode': 401, 'body': 'Unauthorized'}
This design allows credential rotation without code deployment—critical for a production system where service keys might be compromised.
CloudFront Header Stripping and API Gateway Direct Access
Our first attempt to call SCC's event creation endpoint failed silently because CloudFront—configured with a strict origin header whitelist—was stripping the X-Service-Key header before requests reached the Lambda.
Problem:
# This fails (CloudFront strips headers):
POST https://api.sailjada.com/scc/events
X-Service-Key: [service_key]
# CloudFront Distribution: [dist-id]
# Origin: S3 bucket sailjada.com + API Gateway mixed origin
# Configured to only pass through specific headers
Solution: Direct API Gateway Access
Instead of routing through CloudFront, we identified the API Gateway endpoint directly:
# API Gateway endpoint (bypasses CloudFront header stripping):
POST https://[api-gateway-id].execute-api.[region].amazonaws.com/prod/scc/events
X-Service-Key: [service_key]
This works because API Gateway doesn't strip headers the same way CloudFront does when configured for origin request policies. The tradeoff: direct API Gateway access doesn't benefit from CloudFront caching or DDoS protection, so this pattern is only used for internal service-to-service calls, not public-facing endpoints.
S3 and CloudFront: Guest-Facing Page Deployment
Guest confirmation pages are generated as static HTML and uploaded to the S3 bucket backing our CloudFront distribution:
# File written to:
/tmp/jada-guest-[uuid].html
# Uploaded to S3 bucket: sailjada.com
# Object path: /g/[booking-id].html
# CloudFront distribution: [dist-id]
# Invalidation pattern: /g/[booking-id].html
# CloudFront invalidation command (handled by Lambda):
aws cloudfront create-invalidation \
--distribution-id [dist-id] \
--paths "/g/[booking-id].html"
CloudFront invalidations are necessary because browser caching could serve stale content. The invalidation is synchronous in our workflow—we wait for CloudFront to report the object as invalidated before returning success to the booking system.
Multi-System Event Creation Flow
Here's the orchestration pattern used to create all four artifacts from a single booking trigger:
- JADA Internal Calendar Entry: POST to calendar Lambda with
X-Dashboard-Tokenheader, includes date, crew list, and booking metadata. - ShipCaptainCrew Event: POST directly to API Gateway (bypassing CloudFront) with
X-Service-Keyheader. SCC auto-generates and sends magic links to all crew members listed in the event payload. - Guest Confirmation Page: Generate HTML locally, upload to S3 under
/g/prefix, invalidate CloudFront cache. - Crew Notification Email: Send via SES with BCC to business operations contact, includes crew confirmation link and booking details.
All four operations happen in parallel where possible (calendar and SCC are independent), with email sent last to ensure all resources are live before crew receives the confirmation request.
Key Infrastructure Details
- S3 Bucket:
sailjada.com(serves all static content including guest pages) - CloudFront Distribution: Configured with mixed origins (S3 + API Gateway) for content delivery and API routing
- Lambda Execution Roles: Dashboard Lambda and SCC Lambda have separate IAM roles; only dashboard Lambda can invoke calendar operations
- Environment Variables: DASHBOARD_TOKEN and SERVICE_KEY_HASH stored in Lambda environment, rotated independently
- API Gateway: Direct endpoint used for internal service calls; CloudFront-fronted endpoint used for public APIs
Why This Architecture
The multi-endpoint approach (CloudFront + direct API Gateway) might seem redundant, but it solves a real operational need: public-facing APIs need DDoS protection and caching, while internal service-to-service calls need to pass custom headers that a security-conscious CloudFront configuration would strip. Rather than compromising the public API security posture, we maintain both paths.
The service key hashing pattern in SCC Lambda allows credentials to be rotated by updating Lambda environment variables without code deployment—critical for a system that integrates with crew scheduling and payment.
What's Next
Future work includes:
- Implementing idempotency keys for all three API calls to handle retries safely
- Adding observability (CloudWatch) to track multi-system sync failures
- Consolidating API authentication to a single token format (currently mixing Dashboard