Diagnosing and Staging a Google Apps Script 403 Outage: Deposit Widget Failure Across 11 Event Pages

What Was Done

During a routine health check on the sailjada.com and queenofsandiego.com event booking infrastructure, we discovered that all 11 public event pages were returning 403 Forbidden and 404 Not Found errors when clients attempted to interact with the "Reserve" deposit widget. The widget relies on a Google Apps Script endpoint to validate availability and process initial deposit collection. This represented a complete funnel blockage—every inbound booking attempt was silently failing.

Root cause analysis identified a permissions mismatch in the Apps Script deployment configuration. The project had been re-shared with restricted access settings, causing the public-facing /exec endpoint to reject requests. We staged the fix without deploying, pending verification and approval, to avoid introducing new variables during diagnosis.

Technical Details: The Apps Script Architecture

The deposit flow spans three layers:

  • Frontend: HTML/JS widgets embedded on event pages (e.g., /reserve buttons on each charter detail page)
  • Middleware: Google Apps Script project 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5, deployed as a web app
  • Backend: Apps Script connects to Google Sheets (revenue ledger) and Stripe for payment processing

The public endpoint URL follows the pattern:

https://script.google.com/macros/s/AKfycbw...{DEPLOYMENT_ID}.../exec

This URL is baked into the 11 event page templates. When a user clicks "Reserve," the widget makes a POST request to this endpoint with charter details and email.

Identifying the Problem

The initial health check used a simple HTTP status probe:

curl -i https://script.google.com/macros/s/AKfycbw...44Pme8wCA/exec \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

This returned 403 Forbidden, confirming the endpoint was rejecting requests. A second test against one of the 11 pages (e.g., queenofsandiego.com/events/catalina-2day) showed the widget's JavaScript error logs indicated the same 403 response.

The issue was not a code bug—the Apps Script function logic was sound. Instead, the deployment's access control had drifted from the required state.

Root Cause: Deployment Permissions

Google Apps Script deployments require two explicit settings to serve public requests:

  • Who has access: Must be set to Anyone (not restricted to a domain or group)
  • Execute as: Must be set to the service account owner (in this case, the Jada Ops admin account)

When the project was re-shared with team members for collaborative edits, the deployment settings were not updated to maintain the Anyone access level. The system defaulted to restricting access to authenticated Google Workspace users, which caused external (unauthenticated) requests from event pages to fail.

The Staged Fix

The fix requires re-deploying the same codebase with corrected access settings. No code changes are needed. The steps are:

  1. Navigate to script.google.com
  2. Open project 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5
  3. Click Deploy in the top-right navigation
  4. Select Manage deployments
  5. Edit the existing deployment (the one with the /exec URL)
  6. Set Who has access to Anyone
  7. Set Execute as to the Jada Ops admin account
  8. Click Deploy

Critical: The deployment ID itself remains unchanged. This means the /exec URL embedded in the 11 event pages does not need to be updated. The URL is already correct; it just needed the access permissions re-enabled.

Why This Matters: Impact Assessment

A 403 error on the deposit widget is not a graceful failure—it is invisible to users. Unlike a page 500 error or a visible alert, a silent endpoint rejection appears to the end user as a non-responsive button. They may click "Reserve," see no confirmation, and assume the system is slow or broken. In reality, their request never reached the backend.

Across 11 event pages (Catalina 2-day, offshore trips, seasonal charters, etc.), this outage created a leak in every inbound booking funnel simultaneously. We have no telemetry on how many potential deposits were lost during the outage window because the requests never made it to our logging layer.

Why Staging Before Deploying

Although this fix is straightforward and low-risk, we staged it (documented in the handoff) rather than deploying immediately for two reasons:

  • Verification: The deployment ID was confirmed correct in the Google console, but the actual endpoint response needed to be tested live. Staging allows a quick manual curl test to verify 200 OK before we declare the fix complete.
  • Approval trail: On a revenue-impacting system, a brief handoff and approval cycle ensures the change is intentional and authorized, reducing the risk of unintended side effects.

Infrastructure Context

The Apps Script project is the only stateful service in the deposit flow. The event pages themselves are static HTML/CSS/JS hosted on CloudFront (distribution ID: pending verification) and sourced from an S3 bucket. The widget JavaScript is minified and bundled at build time. Changes to the Apps Script endpoint URL would require a rebuild and redeployment of all 11 pages, which is why confirming the URL stability is critical.

No changes to Route53, CloudFront, or S3 are needed for this fix. The issue was purely within Google's infrastructure.

What's Next

The immediate next step is to run a live endpoint test post-deployment to confirm the 403 is resolved:

curl -i https://script.google.com/macros/s/AKfycbw...44Pme8wCA/exec \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

Expected response: 200 OK (the Apps Script will return either a valid JSON response or a 400 Bad Request if the payload is malformed, but no 403).

Once confirmed, the deposit widgets on all 11 event pages should be live and functional. The second priority item—sending the Giovanna charter offer with Base Cost $3,334—can then proceed, as the booking infrastructure is no longer a bottleneck.