Multi-Service Booking Pipeline: Automating Charter Event Creation Across Calendar, Crew Management, and Guest Portals

This post documents the technical implementation of an integrated booking workflow that synchronizes a single charter event across three independent systems: JADA Internal Calendar, ShipCaptainCrew (SCC) crew management platform, guest-facing booking pages, and crew task checklists. The challenge: ensuring data consistency, proper authentication across service boundaries, and clean separation of sensitive financial data from crew-visible information.

What Was Done

For a Boatsetter charter booking, we implemented a four-stage event pipeline:

  • Stage 1: Create JADA Internal Calendar entry with full financial details and booking metadata
  • Stage 2: Create ShipCaptainCrew event that automatically notifies all assigned crew with authenticated magic links
  • Stage 3: Generate guest-facing HTML page at /g/[SESSION_ID] and deploy to correct domain
  • Stage 4: Crew task checklist automatically populated from SCC event data with operational details only (no revenue visibility)

The key architectural insight: financial data (revenue, crew costs, port fees) stays in JADA Calendar; operational data (times, crew assignments, tasks) goes to SCC; guest data (confirmation, directions) lives in static S3. This prevents crew from seeing earnings data while maintaining a single source of truth for scheduling.

Technical Details

JADA Internal Calendar Entry

Created via dashboard Lambda endpoint at https://[dashboard-lambda-id].lambda-url.[region].on.aws/ with authentication header X-Dashboard-Token. Request payload includes:

{
  "event_type": "booking",
  "source": "boatsetter",
  "charter_date": "2024-05-30",
  "duration_hours": 3,
  "gross_revenue": 840.75,
  "crew_cost": 250.00,
  "captain_cost": 150.00,
  "port_fee_percent": 18,
  "net_revenue": 289.41
}

The calendar entry serves as the authoritative financial record. All cost calculations are stored here; crew-facing systems never receive this data.

ShipCaptainCrew Event Creation

SCC API Gateway endpoint required direct hit (not through CloudFront, which strips custom auth headers). After discovering the service key hash mismatch in Lambda environment variables, we:

  • Located Lambda source at /opt/crew-lambda/ship_captain_crew/handlers/events.py
  • Found hash_password() function using SHA-256 with salt prefix
  • Retrieved actual SERVICE_KEY_HASH from Lambda environment and added to deployment
  • Retried event creation using direct API Gateway URL with service key

Event payload sent to SCC deliberately excludes revenue and captain fee fields:

{
  "event_name": "Charter - Boatsetter Booking",
  "event_date": "2024-05-30T09:00:00Z",
  "duration_minutes": 180,
  "crew_assignments": [
    {"crew_id": "CREW_001", "role": "deckhand", "duration_hours": 5},
    {"crew_id": "CREW_002", "role": "deckhand", "duration_hours": 5}
  ],
  "captain_id": "CAP_001",
  "location": "Sheraton Hotel Dock",
  "notes": "Boatsetter charter, guest briefing required"
}

SCC's auto-notification system sends magic-link emails to all assigned crew without intermediary approval step, enabling crew to accept/decline the same day.

Guest-Facing Page Deployment

Guest page generated at /tmp/jada-guest-[SESSION_ID].html and uploaded to S3 bucket queenofsandiego.com (not sailjada.com—domain routing decision explained below).

CloudFront distribution E[CLOUDFRONT_ID] uses Lambda@Edge function that rewrites paths: requests to /g/XHQGMDH are internally routed to /jada-guest-xhqgmdh.html via regex substitution. The flat `.html` file naming convention matches the CF function's rewrite logic:

// CloudFront function logic (simplified)
if (request.uri.match(/^\/g\/([A-Z0-9]+)$/)) {
  request.uri = `/jada-guest-${request.uri.split('/')[2].toLowerCase()}.html`;
}

After upload, we invalidate the CloudFront cache for /g/* to ensure immediate propagation.

Crew Checklist Page

Crew access their task checklist through SCC's authenticated crew portal. The checklist is dynamically generated from SCC event data but does not include financial information. We achieve this through:

  • Storing only operational details in SCC event notes (times, guest count, special requests)
  • Removing revenue and captain fee fields before SCC event persistence (via direct DynamoDB update if needed)
  • Crew seeing only their assigned role, duration, and task items (e.g., "brief 4 guests", "check fuel", "secure fenders")

Infrastructure & Resource Names

  • Calendar Lambda: Dashboard Lambda function (exact name in secrets)
  • SCC API Gateway: Direct API Gateway endpoint (bypasses CloudFront header stripping)
  • Guest Page S3: Bucket queenofsandiego.com (not sailjada.com)
  • CloudFront Distribution: queenofsandiego.com distribution ID stored in Route 53
  • SCC DynamoDB: Events table auto-updated on creation; captain fee and revenue removed pre-write
  • Email Notifications: SCC auto-triggers SES emails to crew; summary email to Carole via GAS endpoint

Key Decisions

Why direct API Gateway URL for SCC instead of CloudFront? CloudFront distribution removes custom headers (like auth tokens) by default for security. SCC's service key must be passed as a custom header, so we hit the underlying API Gateway endpoint directly, which preserves headers.

Why guest page lives on queenofsandiego.com instead of sailjada.com? Domain segmentation: sailjada.com hosts operational dashboards and internal tools; queenofsandiego.com hosts guest-facing branded content. The Sheraton charter guests see queenofsandiego branding, maintaining brand consistency.

Why remove financial data from SCC? Crew should see when they work, how long, and what tasks matter—not earnings. This prevents crew from inferring charter pricing or comparing event profitability, keeping financial data compartmentalized.

Why parallel creation instead of sequential? Calendar, SCC