EcoWorld Academy Platform — Deployment Guide
Viroway Ltd | Phase 1
Prerequisites
- AWS CLI installed and configured:
aws configure - Node.js 20+ installed:
node --version - AWS CDK installed:
npm install -g aws-cdk - Active HubSpot account with Private Apps access
Step 1 — HubSpot setup (15 minutes)
1.1 Create Private App
- In HubSpot: Settings → Integrations → Private Apps → Create Private App
- Name:
EcoWorld Academy Sync - Description:
Syncs Academy learner registrations as HubSpot contacts - Required scopes:
crm.objects.contacts.readcrm.objects.contacts.writecrm.objects.companies.readcrm.objects.companies.writecrm.schemas.contacts.read
- Click Create app → copy the access token
1.2 Add custom Company properties
In HubSpot: Settings → Properties → Company properties → Create property
| Property name (internal) | Label | Type |
|---|---|---|
ecoworld_company_code | EcoWorld company code | Single-line text |
ecoworld_seats_purchased | Academy seats purchased | Number |
ecoworld_course_assigned | Course assigned | Single-line text |
ecoworld_cohort_start | Cohort start date | Date |
ecoworld_enrolment_status | Academy enrolment status | Dropdown: Pending / Active / Completed |
ecoworld_completion_pct | Training completion % | Number |
ecoworld_certificates_issued | Certificates issued | Number |
ecoworld_last_dashboard_sync | Last dashboard sync | Date |
1.3 Add custom Contact properties
| Property name (internal) | Label | Type |
|---|---|---|
ecoworld_user_id | EcoWorld user ID (Cognito) | Single-line text |
ecoworld_completion_pct | Training completion % | Number |
ecoworld_certificate_issued | Certificate issued date | Date |
ecoworld_last_active | Last active in Academy | Date |
1.4 Populate company records
For each client company:
- Find their Company record in HubSpot
- Set
ecoworld_company_codeto a unique code (e.g.,NORSK-HYDRO-2026) - Set
ecoworld_seats_purchased,ecoworld_course_assigned,ecoworld_cohort_start - This code is what you include in the learner invite link:
https://academy.ecoworld.ai/register?company=NORSK-HYDRO-2026
Step 2 — Store secrets in AWS (5 minutes)
# HubSpot Private App token
aws secretsmanager create-secret \
--name ecoworld/hubspot-token \
--secret-string '{"token":"YOUR_HUBSPOT_TOKEN_HERE"}' \
--region eu-north-1
# HubSpot Portal ID (find in HubSpot Settings → Account Setup)
aws secretsmanager create-secret \
--name ecoworld/hubspot-portal-id \
--secret-string '{"portalId":"YOUR_PORTAL_ID"}' \
--region eu-north-1
Step 3 — Deploy AWS infrastructure (10 minutes)
cd infra
# Install CDK dependencies
npm init -y
npm install aws-cdk-lib constructs
# Bootstrap CDK (first time only, per account/region)
npx cdk bootstrap aws://YOUR_ACCOUNT_ID/eu-north-1
# Preview what will be created
npx cdk diff
# Deploy everything
WEBFLOW_ORIGIN=https://academy.ecoworld.ai npx cdk deploy EcoWorldAcademy
Save the outputs — you'll need them for Step 4:
EcoWorldAcademy.UserPoolId = eu-north-1_XXXXXXXXX
EcoWorldAcademy.UserPoolClientId = XXXXXXXXXXXXXXXXXXXXXXXXXX
EcoWorldAcademy.ApiEndpoint = https://XXXXXXXXXX.execute-api.eu-north-1.amazonaws.com/v1
EcoWorldAcademy.CognitoHostedUI = https://ecoworld-prod.auth.eu-north-1.amazoncognito.com
Step 4 — AWS S3 + CloudFront Hosting (15–20 minutes)
We host the internal Academy/Admin UI using:
- AWS S3 (static website hosting)
- CloudFront CDN
- ACM SSL certificate
- Cloudflare DNS
4.1 Frontend config
Open:
frontend/ecoworld-academy.js
Update the CONFIG section:
const CONFIG = {
userPoolId: "eu-north-1_XXXXXXXXX",
clientId: "XXXXXXXXXXXXXXXXXXXXXXXXXX",
cognitoDomain: "ecoworld-prod.auth.eu-north-1.amazoncognito.com",
apiBase: "https://XXXXXXXXXX.execute-api.eu-north-1.amazonaws.com/v1",
siteUrl: "https://academy.ecoworld.ai",
postLoginPath: "/academy"
};
4.2 Build the frontend bundle
Example:
npm run build
Output should go into:
dist/
or:
build/
depending on the frontend tooling.
4.3 Create S3 bucket
Bucket name:
ecoworld/academy/admin
Enable:
- Static website hosting
- Private bucket access (recommended)
- CloudFront Origin Access Control (OAC)
Do NOT expose the bucket publicly.
4.4 Create ACM certificate
In AWS Certificate Manager (ACM):
Region:
us-east-1
Request certificate for:
academy.ecoworld.ai
admin.ecoworld.ai
Validate via DNS using Cloudflare.
CloudFront requires ACM certificates in us-east-1.
4.5 Create CloudFront distribution
Origin:
S3 bucket
Settings:
- Redirect HTTP → HTTPS
- Enable compression
- Default root object:
index.html
Attach the ACM certificate.
Custom domains:
academy.ecoworld.ai
admin.ecoworld.ai
4.6 Add Cloudflare DNS
In Cloudflare:
Add CNAME records:
academy.ecoworld.ai → xxxxxx.cloudfront.net
admin.ecoworld.ai → xxxxxx.cloudfront.net
Enable:
Proxy = ON
SSL/TLS mode:
Full (strict)
4.7 Deploy frontend files
Upload build output to S3:
aws s3 sync dist/ s3://academy-admin-frontend-prod --delete
Then invalidate CloudFront cache:
aws cloudfront create-invalidation \
--distribution-id YOUR_DISTRIBUTION_ID \
--paths "/*"
4.8 Future authentication
Later we will add:
- Cognito login protection
- Role-based access
- Company-level permissions
- MFA
- SSO (Azure AD / Okta / Google Workspace)
for:
admin.ecoworld.ai
while:
academy.ecoworld.ai
remains public-facing.
Step 5 — Test the full flow (30 minutes)
5.1 Create a test company in HubSpot
- Add company:
Test Company Ltd - Set
ecoworld_company_code=TEST-2025 - Set
ecoworld_seats_purchased=5
5.2 Test learner registration
- Open:
https://academy.ecoworld.ai/register?company=TEST-2025 - Should redirect to Cognito hosted login
- Register a new user with a real email
- Confirm email
- Check:
- ✓ DynamoDB
ecoworld-progresshas a newLEARNER_PROFILErow - ✓ HubSpot has a new Contact linked to
Test Company Ltd - ✓ Cognito user has
custom:hubspot_company_idattribute set
- ✓ DynamoDB
5.3 Test the workbook
- Log in and navigate to
/academy - Type in a workbook exercise textarea (must have
data-eco-exercise="1.1:exercise-name") - After 2 seconds, should auto-save
- Refresh the page — answer should be restored
- Progress bar should show updated %
5.4 Test the admin dashboard
- In Cognito console, set a user's
custom:roleattribute toadmin - Log in as that user
- Navigate to the dashboard page
- Should see all learners and their progress
Monitoring and troubleshooting
CloudWatch Logs
Each Lambda writes to its own log group:
/aws/lambda/ecoworld-prod-post-confirmation
/aws/lambda/ecoworld-prod-hubspot-sync
/aws/lambda/ecoworld-prod-save-answer
/aws/lambda/ecoworld-prod-get-progress
/aws/lambda/ecoworld-prod-risk-score
/aws/lambda/ecoworld-prod-dashboard
Common issues
| Issue | Likely cause | Fix |
|---|---|---|
| HubSpot sync not firing | Cognito trigger not attached | Re-deploy CDK stack |
| Company not found in HubSpot | ecoworld_company_code not set | Add the custom property in HubSpot and set it on the company record |
| Token exchange fails | Redirect URI mismatch | Add your Webflow URL to the Cognito app client callback URLs |
| CORS errors in browser | Wrong WEBFLOW_ORIGIN | Update the env var and re-deploy |
| Progress not loading | User not logged in | Check Cognito token expiry (1hr default) |
Cost estimate (monthly)
At 200 active learners across 10 client companies:
| Service | Estimated cost |
|---|---|
| AWS Cognito | Free (first 50k MAU) |
| DynamoDB (on-demand) | ~€2 |
| Lambda (200 users × 50 calls/day) | Free (1M calls/month included) |
| API Gateway | ~€1 |
| CloudWatch Logs | ~€1 |
| Secrets Manager | ~€0.40 |
| Total | ~€4–5/month |
Next phases
- Phase 2: Supplier questionnaire portal (suppliers get a link, fill form, responses stored)
- Phase 3: Full supplier monitoring dashboard with CAP tracking
- Phase 4: LearnWorlds → AWS completion sync (so quiz scores flow into the unified dashboard)