Auvelity DTC Web Platform

Technical handoff documentation — forms, APIs, redirects, accounts, and the chatbot.

Repositoryauvelity.com
StackGatsby 5 (React) → static HTML/CSS/JS on AWS S3 behind CloudFront
BrandsMDD (major depressive disorder) & AAD (Alzheimer's agitation)
Back endAxsome-hosted form APIs + one Lambda function (ours)

Platform Overview

Most forms send data to an API that Axsome IT runs. We own the front end (fields, validation, request shaping); Axsome handles storage and fulfillment. The one exception is the MDD advocate form — that goes to a Lambda we built called advocateProxy, which pushes data into Salesforce.

First question when a form breaks: which back end does it talk to — Axsome's API or our Lambda? The debugging path and the people you need are completely different.

How Forms Talk to the Back End

Shared patterns across all forms:

Watch out: a 200 HTTP status doesn't always mean success. Some newer forms also check for status: 200 or success: true in the response body. If a form silently fails, inspect the response body — don't just check res.ok.

Payload formats

Forms were built at different times and use two different payload formats:

Endpoint Reference

Env varPurposeSource filePayload
GATSBY_AXS_API_MDD_STAY_CONNECTEDMDD email sign-upform.jsflat
GATSBY_AXS_API_MDD_ADVOCATESMDD advocate program → our Lambdaadvocate-form.jsflat
GATSBY_AXS_API_PSK_STEP_1Patient Starter Kit — first submit (returns row ID)savingsCard.js, patient-starter-kit/mixed
GATSBY_AXS_API_PSK_STEP_2Patient Starter Kit — attaches phone to row from Step 1patient-starter-kit/mixed
GATSBY_AXS_API_SAVINGS_ENROLLEnroll a new savings/copay cardsavingsCard.js, cp-onmyside-savings-card-form.jsstructured
GATSBY_AXS_API_SAVINGS_ACTIVATEActivate an existing card(same components)structured
GATSBY_AXS_API_AADAAD "Stay Up To Date" sign-up + surveycp-sign-up-form.js, cp-onmyside-sign-up-form.jsstructured
GATSBY_AXS_API_AAD_ADVOCATESAAD ambassador programcp-ambassador-sign-up-form.jsstructured

Savings / Copay Card Form

This is the most critical form — it enrolls patients into the copay program and activates cards. Lives in src/components/forms/savingsCard.js.

How it decides what to do

Enroll / Activate payload shape (AAD version)

{
  "ConsentPatientServices": true,
  "ConsentMarketing": "Y | N",
  "ConsentSms": "Y | N",
  "DateOfBirth": "YYYY-MM-DD",
  "EmailAddress": "...",
  "FirstName": "...",
  "LastName": "...",
  "MobilePhoneNumber": "##########",   // digits only
  "ZipCode": "...",
  "GroupId": "AUVRET",
  "Indication": "1 (MDD) | 2 (AAD)",
  "InsuranceType": 1,
  "Product": "Auvelity",
  "Source": "CultHealth",
  "CardId": "..."                       // activate only
  // "Caregiver": { ...Survey }         // AAD only
}

Values that are easy to mix up

FieldValue
Indication1 = MDD, 2 = AAD
GroupIdAlways AUVRET
SourceAlways CultHealth

This form handles sensitive patient data. The payload includes names, dates of birth, phone numbers, ZIP codes, insurance type, and card IDs — that's PII and PHI. Never log the full payload to analytics or the console in production, never put any of it in a URL, and always talk to Axsome IT before changing field names or structure. Their API checks field names strictly — a rename will silently fail.

Other Forms (Quick Overview)

AAD "Stay Up To Date"

Structured envelope format. Includes a four-question care-partner survey.

{
  "FormName": "StayUpToDate",
  "Product": "Auvelity",
  "Source": "Auvelity_DTC",
  "Data": {
    "FirstName": "...", "LastName": "...",
    "EmailAddress": "...", "MobilePhoneNumber": "...",
    "OnProduct": true, "ConsentToAxsomeTerms": true,
    "ConsentMarketing": true, "ConsentSms": true,
    "RecaptchaResponse": "..."
  },
  "Survey": [{ "index": 1, "question": "...", "response": "<int|null>" }],
  "FormUrl": "<current page>"
}

AAD Ambassador

Same structured format. FormName is Ambassador_Aad, has a one-question survey, relationship field defaults to 7.

MDD Advocates

Flat object wrapped in { data: [ <fields> ] }. Phone is cleaned to 10-digit US number, wiped if user didn't opt into ambassador program. Goes to our advocateProxy Lambda — not Axsome.

MDD Stay Connected

Simplest form. Posts flat object with formtype, source-url, FormURL, and env tacked on.

Patient Starter Kit

Two-step process: Step 1 submits patient info and returns a row ID. Step 2 attaches phone number to that row. If a phone number is missing, check whether Step 2 ran and received the correct row ID.

The advocateProxy Lambda

The MDD advocate form doesn't talk to Axsome — GATSBY_AXS_API_MDD_ADVOCATES points at our own Lambda. It acts as a trusted middleman between the browser and Salesforce Data Cloud (so credentials don't live client-side).

Server-side flow

  1. Get a Salesforce access tokenPOST to https://ipgh.my.salesforce.com/services/oauth2/token with client_credentials grant (uses CLIENT_ID / CLIENT_SECRET from env vars).
  2. Exchange for Data Cloud tokenPOST to https://ipgh.my.salesforce.com/services/a360/token with grant type urn:salesforce:grant-type:external:cdp. Returns a scoped token and instance URL.
  3. Push data — Sends form data unchanged to https://<instance_url>/api/v1/ingest/sources/Auvelity_Ambassador_Program/AuvelityFormFields.

Things to know

Where credentials live: CLIENT_ID and CLIENT_SECRET are stored in the Lambda's Configuration → Environment variables in AWS. They're not in this repo or any .env file. To view or rotate: AWS Lambda access + IPG Health Salesforce team (connected app).

Double-check: The source is named Auvelity_Ambassador_Program, which sounds like it could overlap with the AAD ambassador form. Confirm whether the AAD ambassador form also uses advocateProxy or goes to Axsome like the others.

Accessing & Deploying the Lambda

How to find the Lambda in AWS

  1. Sign in to the AWS Console at https://us-east-1.console.aws.amazon.com/ (use the team credentials stored in Bitwarden).
  2. Make sure the region selector in the top-right shows US East (N. Virginia) — us-east-1.
  3. In the search bar at the top, type Lambda and select the Lambda service.
  4. In the Functions list, search for advocateProxy and click it.

Viewing and editing the code

  1. On the function page, scroll down to the Code source panel. If the deployment package is small enough, you'll see an inline editor with the code.
  2. Edit directly in the browser, or download the code with Actions → Export function (.zip) to work locally.
  3. If you edit in the browser, click Deploy (the orange button above the editor) to publish your changes. The new version goes live immediately.

Deploying an update from a local .zip

If the code is too large for the inline editor or you prefer working locally:

# 1. Zip your updated code (index.mjs or index.js + any dependencies)
zip -r advocateProxy.zip .

# 2. Deploy via the AWS CLI
aws lambda update-function-code \
  --function-name advocateProxy \
  --zip-file fileb://advocateProxy.zip \
  --region us-east-1

# 3. (Optional) Publish a new version snapshot
aws lambda publish-version \
  --function-name advocateProxy \
  --region us-east-1

Or upload through the console: Code source → Upload from → .zip file.

Environment variables (credentials)

  1. On the Lambda page, go to Configuration → Environment variables.
  2. You'll see CLIENT_ID and CLIENT_SECRET. Click Edit to view or rotate them.
  3. After changing a value, click Save. Changes take effect on the next invocation (no redeploy needed).

Checking logs when something breaks

  1. On the Lambda page, go to the Monitor tab → View CloudWatch logs.
  2. You'll see log streams sorted by most recent. Click the latest one.
  3. Look for the three-step flow: Salesforce token → Data Cloud token → data push. Whichever step threw an error is where it broke.

Testing a change without affecting production

The simplest approach:

  1. Create a test event in the Lambda console (Test tab → Create new event).
  2. Paste a sample form payload as the event body.
  3. Click Test. The result and logs appear inline — no real form submission needed.

For a full end-to-end test, point the staging GATSBY_AXS_API_MDD_ADVOCATES env var at the Lambda's function URL and submit the form in the staging environment.

The Lambda is live immediately on deploy. There's no separate staging Lambda — changes hit production as soon as you click Deploy or run update-function-code. Test with the built-in test events first, or create a separate function for staging if the risk is too high.

AWS Edge Redirects

The redirect logic is not in this repo. The big set of redirects and canonical hostname enforcement run on Lambda@Edge functions attached to CloudFront:

SiteLambda function
DTC (auvelity.com)AddSlashRedirect
HCP (GATSBY_HCPURL)AddSlashRedirectHCP

These handle:

If you can't find a redirect in static/_redirects or gatsby-browser.js, it's almost certainly in AddSlashRedirect (DTC) or AddSlashRedirectHCP (HCP). Editing requires AWS Lambda@Edge access.

Repo-Level Redirects

Only two redirects live in the repo, each defined in two places that must stay in sync:

FromToType
/aad/ambassador/aad/auvelity-ambassador-program301
/aad/onmyside-registration/aad/on-my-side-savings-support-form301

Defined in:

If you add or change an /aad redirect, update both files. Trailing slashes are stripped everywhere (trailingSlash: "never" in Gatsby config + onCreatePage hook in gatsby-node.js).

Intentional Page Reload

gatsby-browser.js forces a full page reload on route changes. This is intentional — don't remove it.

Gatsby's client-side navigation normally swaps content without a full page load, which means scripts that run on page load (GTM, the chatbot) won't re-initialize with correct IDs when navigating between sections. The forced reload ensures GTM container ID and Ostro chatbot instance ID are always current.

Canonical URLs & Crawling

Environments & S3 Buckets

EnvS3 bucketCloudFront distribution
stagingcult-auvelity-dtc-stagingE279QEURGZIA4R
uatcult-auvelity-dtc-uatE2BILD7F3ITHQC
uat2cult-auvelity-dtc-uat-2E3TXUJSI7TYKGU
productioncult-auvelity-dtc-productionE2KA9NEW4LOPQU

Building & Deploying

First-time AWS setup

Creating an IAM user for a new team member

  1. Sign in to the AWS Console with an admin account.
  2. Go to IAM → Users → Create user.
  3. Enter a username (e.g. firstname.lastname) and check Provide user access to the AWS Management Console if they need console access.
  4. On the permissions step, select Attach policies directly and add:
    • AmazonS3FullAccess (for deploy sync to buckets)
    • CloudFrontFullAccess (for cache invalidation)
    • AWSLambda_FullAccess (if they need Lambda access for advocateProxy or edge functions)
  5. Click through to create the user.
  6. Go to the new user → Security credentialsCreate access key → select Command Line Interface (CLI).
  7. Copy both the Access Key ID and Secret Access Key (the secret is only shown once). Send them securely to the new team member.

Configuring the CLI on a new machine

aws configure

It will prompt for four values:

This saves credentials to ~/.aws/credentials — you only need to do it once per machine.

Build commands

# Install dependencies
npm install

# Build for target environment
npm run build:staging
npm run build:uat
npm run build:production

Reads the matching .env.* file — endpoints, flags, and keys are set automatically.

Deploy commands

./deploy.js staging
./deploy.js uat
./deploy.js production

The deploy script reads deploy.config.js for the S3 bucket and CloudFront distribution ID mappings for each environment. It syncs the build output to the correct bucket and invalidates the CloudFront cache automatically. Cache invalidation takes 1–2 minutes to propagate.

Always build before deploying. The deploy script pushes whatever is in public/ — deploying without building first pushes stale files. Double-check your target environment, especially for production.

Access You Need

Credentials vault: All account information (API keys, vendor logins, AWS credentials, etc.) can be accessed via the dev Bitwarden account at https://banners.culthealth.com/vault/#/login.

IDs & Keys in the Code

WhereWhat it is
GATSBY_AXS_API_REFRESH_TOKENAxsome refresh token — defined but unused
GATSBY_AXS_AUTH_CLIENTAxsome auth client ID — defined but unused
GATSBY_AIM_XR_KEY_APIAIM/XR API key — defined but unused
GATSBY_RECAPTCHAGoogle reCAPTCHA site key
GATSBY_RXDEFINEIDRxDefine / Ostro chatbot instance ID
Five9 auth5044dcf3-4fcc-46e6-a465-a689f7b04ac8
Ostro ID (AAD)f3015744-bc50-4a2e-b672-b934c541d04d
Ostro ID (MDD)28519827-ad25-4810-911f-b3e295eea6ba
advocateProxy env varsSalesforce connected-app credentials — in AWS Lambda config only

Something to sort out: The three Axsome auth variables (REFRESH_TOKEN, AUTH_CLIENT, AIM_XR_KEY_API) are defined in env files but unused in src. The live forms only authenticate via Origin header + reCAPTCHA. Ask Axsome IT if these were supposed to be wired in or if they're leftover cruft.

Chatbot Architecture

The AAD section combines an AI bot (Ostro/RxDefine) with live chat (Five9). It's not live yet — was being tested in staging with the bot ↔ agent handoff working, waiting on final assets and launch date.

The Five9 live chat window never appears on its own. It only opens when a user clicks a link inside the Ostro bot that has a topic parameter.

PartVendorLoaded byControlled by
AI botOstro / RxDefineseo.js, aad/layout.jsGATSBY_RXDEFINEID env var
Live chatFive9 (GiftHealth built the integration)five9-chat.jsGATSBY_ENABLE_FIVE9_CHAT env var

Ostro only loads when showOstro is aad or mdd (separate instance IDs per brand). Five9 only loads when GATSBY_ENABLE_FIVE9_CHAT is "true".

Bot-to-Live-Agent Handoff

Code lives in src/components/five9-chat.js. Here's what happens:

  1. The AI bot renders inside a shadow DOM under #webchatv4.
  2. A click listener is attached to the shadow root.
  3. When a user clicks a link with both /aad and topic= in its href, the listener intercepts it.
  4. The AI bot is closed (TAIlor.toggleOpen(false), RxDefineChat.hideChat(true)).
  5. The Five9 live chat widget (#five9LiveChatWidget) opens instead.
  6. The topic value gets passed to Five9 as attributes: [{ topic }], giving the agent context.

Because the bot loads asynchronously, the code polls every 500ms until #webchatv4 and its shadow root exist, then attaches the listener and stops. The reverse toggle (reopening the AI bot) is handled in auvelity-ui.js.

Five9 Configuration

SettingValue
App ID / DomainOccam Health
CampaignChat_Auvelity
Auth key5044dcf3-4fcc-46e6-a465-a689f7b04ac8
Intake formName + email (both optional, clears between sessions)
Consent18+ disclaimer, stored as Consent, links to Axsome ToU/Privacy
HoursBusiness hours enabled, America/New_York
StylingAuvelity navy #21174b / gold #ffc000 in Poppins + /five9/Five9_Auvelity_Embedded.css

Updating Five9 Styling

When GiftHealth sends style updates (a static HTML folder), most files are irrelevant. The only thing you need is the chat styling values — copy them into the options constant in src/components/five9-chat.js. Ignore the rest of the files.

Launch Checklist

  1. Get the finished content package from Ostro/RxDefine and confirm the AAD instance ID is correct.
  2. Set GATSBY_ENABLE_FIVE9_CHAT=true in the target environment.
  3. Every CTA link inside the bot must have both /aad and topic= in its href — or clicking it won't trigger the live chat switch.
  4. Check business hours and agent routing with the Occam Health / GiftHealth team.

All file paths in this doc are relative to the repo root. Environment variable names refer to .env.development / .env.production. The file env.development.example has the full list of available variables.

Deployment QA Process

After every deployment, coordinate with the QA team to validate the release:

ContactRole
Chad SpielmanFRD functionality check
Joe ShanleyFRD functionality check

Their process:

  1. Perform a full FRD (Functional Requirements Document) check against the live site.
  2. Verify all pages, forms, redirects, and interactions behave as specified.
  3. Raise any issues found in the bug tracker.

Don’t deploy without giving Chad or Joe a heads-up. They need to know what changed so they can focus their testing on the affected areas.

API Testing & Contacts

For any API updates — especially new API functionality — collaborate with the Axsome IT team:

ContactRole
Nathan GoshayPrimary Axsome IT contact for API work
Chris MurphyHead of Axsome Tech (escalation point)

When working on API changes:

Staging vs Production Validation

Both staging and production must be validated before a release goes live. The key steps:

  1. Staging first — deploy to staging and confirm the form payloads are correct (inspect via browser dev tools or CloudWatch logs) and that data is inserting into the database as expected.
  2. Production API testing on UAT — production API endpoints can be tested against the UAT environment. This is typically done a couple of days before launch to catch any discrepancies between staging and production configs.
  3. Payload review — compare the request body sent by the form against the expected shape documented in the endpoint reference. Verify field names, data types, and required values all match.
  4. Database insertion check — confirm with Axsome IT (Nathan) that submitted test records landed correctly in their system.

Don’t skip UAT testing. The production API may have different validation rules or newer field requirements than staging. Testing on UAT a few days before launch gives enough buffer to fix issues without delaying the release.

Accessibility Testing

Accessibility compliance is validated using accessibilitychecker.org. This tool tests against WCAG and AAA standards.

Process

  1. Go to accessibilitychecker.org and select Manual Pages.
  2. List all site pages to be tested (every public-facing URL).
  3. Run the scan — the tool will highlight any problematic features (contrast, alt text, ARIA labels, keyboard navigation, etc.).
  4. Address each flagged issue in the codebase.
  5. Re-deploy to staging and re-scan until all tests pass.
  6. Once compliant, capture two deliverables as proof:
    • Screenshot the dashboard — showing the website is compliant (green/passing state).
    • Download the full report — the PDF/summary export that provides a complete breakdown.

These two files (screenshot + downloaded report) are sufficient proof of WCAG/AAA compliance for client delivery and audit purposes.