Auvelity DTC Web Platform
Technical handoff documentation — forms, APIs, redirects, accounts, and the chatbot.
| Repository | auvelity.com |
| Stack | Gatsby 5 (React) → static HTML/CSS/JS on AWS S3 behind CloudFront |
| Brands | MDD (major depressive disorder) & AAD (Alzheimer's agitation) |
| Back end | Axsome-hosted form APIs + one Lambda function (ours) |
Overview
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.
Forms & APIs
How Forms Talk to the Back End
Shared patterns across all forms:
POSTwithContent-Type: application/json. No auth token — the server checks theOriginheader (set toGATSBY_SITEURL).- API URLs come from
GATSBY_AXS_API_*env vars, baked in at build time. Dev, UAT, and production hit different endpoints automatically. - Every form uses Google reCAPTCHA (
GATSBY_RECAPTCHA). Token is sent asRecaptchaResponseorg-recaptcha-response.
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:
- Flat (older MDD forms) — simple object with metadata fields (
formtype: "BWEBCON",source-url,env). - Structured (newer AAD forms & rebuilt savings flow) — envelope with
FormName,Product,Source,Data,Survey.
Endpoint Reference
| Env var | Purpose | Source file | Payload |
|---|---|---|---|
GATSBY_AXS_API_MDD_STAY_CONNECTED | MDD email sign-up | form.js | flat |
GATSBY_AXS_API_MDD_ADVOCATES | MDD advocate program → our Lambda | advocate-form.js | flat |
GATSBY_AXS_API_PSK_STEP_1 | Patient Starter Kit — first submit (returns row ID) | savingsCard.js, patient-starter-kit/ | mixed |
GATSBY_AXS_API_PSK_STEP_2 | Patient Starter Kit — attaches phone to row from Step 1 | patient-starter-kit/ | mixed |
GATSBY_AXS_API_SAVINGS_ENROLL | Enroll a new savings/copay card | savingsCard.js, cp-onmyside-savings-card-form.js | structured |
GATSBY_AXS_API_SAVINGS_ACTIVATE | Activate an existing card | (same components) | structured |
GATSBY_AXS_API_AAD | AAD "Stay Up To Date" sign-up + survey | cp-sign-up-form.js, cp-onmyside-sign-up-form.js | structured |
GATSBY_AXS_API_AAD_ADVOCATES | AAD ambassador program | cp-ambassador-sign-up-form.js | structured |
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
- Not prescribed yet / government insurance → PSK Step 1 (with
InsuranceTypeandIsPrescribed) - New card sign-up →
SAVINGS_ENROLL - Activating existing card →
SAVINGS_ACTIVATE(withCardId) - Bad card number → new card →
SAVINGS_ENROLLwithCardId/CopayCardIdstripped
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
| Field | Value |
|---|---|
Indication | 1 = MDD, 2 = AAD |
GroupId | Always AUVRET |
Source | Always 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
- Get a Salesforce access token —
POSTtohttps://ipgh.my.salesforce.com/services/oauth2/tokenwithclient_credentialsgrant (usesCLIENT_ID/CLIENT_SECRETfrom env vars). - Exchange for Data Cloud token —
POSTtohttps://ipgh.my.salesforce.com/services/a360/tokenwith grant typeurn:salesforce:grant-type:external:cdp. Returns a scoped token and instance URL. - Push data — Sends form data unchanged to
https://<instance_url>/api/v1/ingest/sources/Auvelity_Ambassador_Program/AuvelityFormFields.
Things to know
- The Salesforce org URL is hardcoded in the Lambda — not an env var.
- Data lands in a source called
Auvelity_Ambassador_Program, objectAuvelityFormFields. Field names must match the schema exactly. - The Lambda doesn't transform data — whatever the form sends is what Salesforce gets.
- Returns
200on success,500on failure. Three steps can fail — check CloudWatch logs.
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
- Sign in to the AWS Console at
https://us-east-1.console.aws.amazon.com/(use the team credentials stored in Bitwarden). - Make sure the region selector in the top-right shows US East (N. Virginia) — us-east-1.
- In the search bar at the top, type Lambda and select the Lambda service.
- In the Functions list, search for
advocateProxyand click it.
Viewing and editing the code
- 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.
- Edit directly in the browser, or download the code with Actions → Export function (.zip) to work locally.
- 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)
- On the Lambda page, go to Configuration → Environment variables.
- You'll see
CLIENT_IDandCLIENT_SECRET. Click Edit to view or rotate them. - After changing a value, click Save. Changes take effect on the next invocation (no redeploy needed).
Checking logs when something breaks
- On the Lambda page, go to the Monitor tab → View CloudWatch logs.
- You'll see log streams sorted by most recent. Click the latest one.
- 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:
- Create a test event in the Lambda console (Test tab → Create new event).
- Paste a sample form payload as the event body.
- 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.
Redirects & Deployment
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:
| Site | Lambda function |
|---|---|
DTC (auvelity.com) | AddSlashRedirect |
HCP (GATSBY_HCPURL) | AddSlashRedirectHCP |
These handle:
www.enforcement — redirects bare domain towww.auvelity.com(SEO: prevents ranking split)- Path redirects & slash normalization — all configured redirects including the
auvelity.com → /mddtransition set
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:
| From | To | Type |
|---|---|---|
/aad/ambassador | /aad/auvelity-ambassador-program | 301 |
/aad/onmyside-registration | /aad/on-my-side-savings-support-form | 301 |
Defined in:
static/_redirects— Netlify-style301!rules, copied into build output.gatsby-browser.js—pathRedirectsmap withwindow.location.replace(). JS-level fallback (brief flash before redirect).
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
gatsby-plugin-react-helmet-canonical-urlssets all canonical tags tohttps://www.auvelity.com(no trailing slash). Actual enforcement is at the edge layer.- The
onPostBuildhook ingatsby-node.jswrites a "block everything"robots.txt— but only forGATSBY_ENV=development. Never run production builds with that flag.
Environments & S3 Buckets
| Env | S3 bucket | CloudFront distribution |
|---|---|---|
| staging | cult-auvelity-dtc-staging | E279QEURGZIA4R |
| uat | cult-auvelity-dtc-uat | E2BILD7F3ITHQC |
| uat2 | cult-auvelity-dtc-uat-2 | E3TXUJSI7TYKGU |
| production | cult-auvelity-dtc-production | E2KA9NEW4LOPQU |
Building & Deploying
First-time AWS setup
Creating an IAM user for a new team member
- Sign in to the AWS Console with an admin account.
- Go to IAM → Users → Create user.
- Enter a username (e.g.
firstname.lastname) and check Provide user access to the AWS Management Console if they need console access. - 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 foradvocateProxyor edge functions)
- Click through to create the user.
- Go to the new user → Security credentials → Create access key → select Command Line Interface (CLI).
- 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:
- Access Key ID — from the IAM user creation step above
- Secret Access Key — from the IAM user creation step above
- Default region —
us-east-1 - Output format —
json
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.
Accounts & Credentials
Access You Need
- Axsome IT — Teams chats/channels for form and endpoint changes. Get these pointed at whoever takes over.
- AWS (CloudFront, S3, Lambda@Edge) — Four distributions/buckets +
AddSlashRedirect/AddSlashRedirectHCPfunctions. advocateProxyLambda — Code, env vars (CLIENT_ID/CLIENT_SECRET), and CloudWatch logs.- Salesforce / IPG Health — Contact for connected app management and credential rotation.
- Redirect FRDs — Chat and Joe have these. Find out where they're stored.
- Five9 / Occam Health — Live-chat campaign, business hours, agent routing (configured on their side).
- Vendor accounts — Ostro/RxDefine (chatbot), OneTrust (cookies), Google reCAPTCHA, GTM, Adobe Typekit, domain verification for Facebook/Google (in
src/components/seo.js).
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
| Where | What it is |
|---|---|
GATSBY_AXS_API_REFRESH_TOKEN | Axsome refresh token — defined but unused |
GATSBY_AXS_AUTH_CLIENT | Axsome auth client ID — defined but unused |
GATSBY_AIM_XR_KEY_API | AIM/XR API key — defined but unused |
GATSBY_RECAPTCHA | Google reCAPTCHA site key |
GATSBY_RXDEFINEID | RxDefine / Ostro chatbot instance ID |
| Five9 auth | 5044dcf3-4fcc-46e6-a465-a689f7b04ac8 |
| Ostro ID (AAD) | f3015744-bc50-4a2e-b672-b934c541d04d |
| Ostro ID (MDD) | 28519827-ad25-4810-911f-b3e295eea6ba |
advocateProxy env vars | Salesforce 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.
AAD Chatbot
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.
| Part | Vendor | Loaded by | Controlled by |
|---|---|---|---|
| AI bot | Ostro / RxDefine | seo.js, aad/layout.js | GATSBY_RXDEFINEID env var |
| Live chat | Five9 (GiftHealth built the integration) | five9-chat.js | GATSBY_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:
- The AI bot renders inside a shadow DOM under
#webchatv4. - A click listener is attached to the shadow root.
- When a user clicks a link with both
/aadandtopic=in itshref, the listener intercepts it. - The AI bot is closed (
TAIlor.toggleOpen(false),RxDefineChat.hideChat(true)). - The Five9 live chat widget (
#five9LiveChatWidget) opens instead. - The
topicvalue gets passed to Five9 asattributes: [{ 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
| Setting | Value |
|---|---|
| App ID / Domain | Occam Health |
| Campaign | Chat_Auvelity |
| Auth key | 5044dcf3-4fcc-46e6-a465-a689f7b04ac8 |
| Intake form | Name + email (both optional, clears between sessions) |
| Consent | 18+ disclaimer, stored as Consent, links to Axsome ToU/Privacy |
| Hours | Business hours enabled, America/New_York |
| Styling | Auvelity 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
- Get the finished content package from Ostro/RxDefine and confirm the AAD instance ID is correct.
- Set
GATSBY_ENABLE_FIVE9_CHAT=truein the target environment. - Every CTA link inside the bot must have both
/aadandtopic=in itshref— or clicking it won't trigger the live chat switch. - 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.
QA & Accessibility
Deployment QA Process
After every deployment, coordinate with the QA team to validate the release:
| Contact | Role |
|---|---|
| Chad Spielman | FRD functionality check |
| Joe Shanley | FRD functionality check |
Their process:
- Perform a full FRD (Functional Requirements Document) check against the live site.
- Verify all pages, forms, redirects, and interactions behave as specified.
- 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:
| Contact | Role |
|---|---|
| Nathan Goshay | Primary Axsome IT contact for API work |
| Chris Murphy | Head of Axsome Tech (escalation point) |
When working on API changes:
- Confirm field names and payload shapes with Nathan before implementation — their API validates strictly and won’t surface errors for mismatched fields.
- For new endpoints or significant changes, loop in Chris Murphy for sign-off.
- Coordinate testing timelines so both sides can verify simultaneously.
Staging vs Production Validation
Both staging and production must be validated before a release goes live. The key steps:
- 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.
- 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.
- 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.
- 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
- Go to accessibilitychecker.org and select Manual Pages.
- List all site pages to be tested (every public-facing URL).
- Run the scan — the tool will highlight any problematic features (contrast, alt text, ARIA labels, keyboard navigation, etc.).
- Address each flagged issue in the codebase.
- Re-deploy to staging and re-scan until all tests pass.
- 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.