Overview
This guide walks you through deploying the StockedUp Discord Auth Automation system to production. The workflow is:
Chargebee Payment → Redirect → Middleware → Discord OAuth → Airtable Update → Discord Bot → Role Assigned
Components
| Component | Description | Port |
|---|---|---|
| Middleware Service | Handles Chargebee redirects, OAuth, and webhooks | 3000 |
| Discord Bot Service | Discord Gateway connection and role management | 3001 |
Table of Contents
- Prerequisites
- Architecture
- Step 1: Discord Developer Setup
- Step 2: Chargebee Configuration
- Step 3: Airtable Setup
- Step 4: Environment Variables
- Step 5: Verification and Testing
- Step 6: Build and Deploy (PebbleHost)
- Troubleshooting
- Optional: Additional Features
- Optional: Cloudflare Tunnel Setup
- Quick Reference
Prerequisites
- Node.js 18+ and npm 8+
- Discord Developer Account: discord.com/developers
- Chargebee account with API access
- Airtable account with a prepared base
- Production domain with SSL certificate
- Server/hosting (Railway, Render, AWS, DigitalOcean, etc.)
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ EXTERNAL SERVICES │
├─────────────────┬─────────────────┬─────────────────────────────────┤
│ Chargebee │ Discord │ Airtable │
│ (Payments) │ (OAuth/Bot) │ (Database) │
└────────┬────────┴────────┬────────┴────────────────┬────────────────┘
│ │ │
│ Webhooks │ OAuth + Gateway │ REST API
│ + Redirects │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ YOUR PRODUCTION SERVER │
├─────────────────────────────┬───────────────────────────────────────┤
│ Middleware Service │ Discord Bot Service │
│ (Port 3000) │ (Port 3001) │
│ │ │
│ • /chargebee/redirect │ • Discord Gateway connection │
│ • /chargebee/webhook │ • Role assignment │
│ • /auth/discord/* │ • guildMemberAdd events │
│ • /health │ • /internal/assign-role │
└─────────────────────────────┴───────────────────────────────────────┘
Step 1: Discord Developer Setup
1.1 Create a Discord Application
- Go to the Discord Developer Portal.
- Create a new application (e.g., "StockedUp Subscription Bot").
- Copy the Client ID → Save as env variable:
DISCORD_CLIENT_ID - Copy the Client Secret → Save as env variable:
DISCORD_CLIENT_SECRET
1.2 Configure OAuth2
- In OAuth2 → General, add the redirect URI:
https://auth.stockedup.university/auth/discord/callback
Save as env variable: DISCORD_REDIRECT_URI
1.3 Create a Bot
- In the Bot section, click Add Bot and reset the token.
- Copy the token → Save as env variable:
DISCORD_BOT_TOKEN
1.4 Configure Bot Permissions
- Enable SERVER MEMBERS INTENT.
- Bot permissions:
MANAGE_ROLESandVIEW_CHANNEL.
1.5 Invite the Bot
- Use OAuth2 → URL Generator with scope
bot. - Select permissions
Manage Roles,View Channels, andSend Messages. - Open the generated URL to authorize the bot in your server.
1.6 Get Server and Role IDs
- Enable Discord Developer Mode.
- Right-click your server → Copy ID → Save as env variable:
DISCORD_GUILD_ID - Right-click the role to assign → Copy ID → Save as env variable:
DISCORD_ROLE_ID
Step 2: Chargebee Configuration
2.1 Get API Credentials
- In Settings → Configure Chargebee → API Keys:
- Copy your site name (subdomain only, e.g.,
stockedup-test) → Save as env variable:CHARGEBEE_SITE - Copy your API key → Save as env variable:
CHARGEBEE_API_KEY
2.2 Configure Checkout Redirect URL
- In Settings → Configure Chargebee → Checkout & Self-Serve Portal, set the redirect URL:
https://auth.stockedup.university/auth/discord?subscription_id={{subscription.id}}&customer_id={{customer.id}}&plan_id={{plan.id}}
This redirects customers to the Discord onboarding page after successful payment.
2.3 Configure Chargebee Webhook (Required)
- In Settings → Configure Chargebee → Webhooks, click Add Webhook.
- Set the Webhook URL to:
https://auth.stockedup.university/chargebee/webhook - Enable "Protect webhook URL with basic authentication"
- Generate or create a secure webhook secret (minimum 32 characters).
- In the Username field, paste your webhook secret (leave Password field empty).
- Save as env variable:
CHARGEBEE_WEBHOOK_SECRET - Select API Version 2
- Recommended: Select specific events (more efficient than "All Events"):
subscription_createdsubscription_changedsubscription_cancelledsubscription_pausedsubscription_resumedsubscription_renewedsubscription_deletedcustomer_createdcustomer_changedcustomer_deleted
- Click Create to save the webhook.
CHARGEBEE_WEBHOOK_SECRET environment variable in Step 4.
2.4 Identify Discord Product Plan IDs
- In Product Catalog → Plans, collect the plan IDs that should trigger Discord roles.
- List them comma-separated → Save as env variable:
DISCORD_PRODUCT_PLAN_IDS - Example:
premium-monthly,premium-yearly,vip-lifetime
Step 3: Airtable Setup
3.1 Create Airtable Base
Create a base (e.g., “StockedUp Subscriptions”) and add the tables below.
3.2 Create Tables
Table 1: Subscriptions
| Field Name | Field Type | Notes |
|---|---|---|
| subscription_id | Text | Primary field, unique identifier |
| customer_id | Text | Chargebee customer ID |
| plan_id | Text | Chargebee plan ID |
| plan_name | Text | Friendly plan name |
| status | Single Select | Options: active, cancelled, paused, expired |
| period_start | Date Time | Current period start |
| period_end | Date Time | Current period end/renewal date |
| active_discord_id | Text | Discord user ID |
| active_discord_username | Text | Discord username |
| active_discord_email | Discord email | |
| active_role_id | Text | Discord role ID |
| active_role_name | Text | Role name |
| role_assigned | Checkbox | Must be Checkbox type |
| role_assigned_at | Date Time | When role was assigned |
| last_verified_at | Date Time | Last verification timestamp |
| auth_url_token | Text | Encrypted token for staff sharing |
| discord_auth_url | URL | Full Discord auth URL |
Table 2: Customers
| Field Name | Field Type | Notes |
|---|---|---|
| customer_id | Text | Primary field, Chargebee customer ID |
| Customer email | ||
| first_name | Text | Customer first name |
| last_name | Text | Customer last name |
Table 3: Audit_Logs
| Field Name | Field Type | Notes |
|---|---|---|
| log_id | Autonumber | Auto-generated |
| subscription_id | Text | Associated subscription |
| customer_id | Text | Associated customer |
| discord_id | Text | Discord user ID |
| verification_id | Text | Link to verification record |
| event | Single Select | See event types below |
| status | Single Select | Options: success, failure |
| message | Long Text | Human-readable message |
| error_details | Long Text | Technical error details |
| ip_address | Text | Client IP |
| user_agent | Text | Client user agent |
| service | Single Select | Options: middleware, bot |
Event Types: oauth_started, oauth_redirect, oauth_callback, oauth_token_exchange, oauth_user_fetch, oauth_completed, oauth_failed, discord_verification_started, discord_verification_verified, discord_verification_failed, discord_verification_revoked, role_assignment_started, role_assignment_completed, role_assignment_failed, subscription_created, subscription_updated, subscription_cancelled, subscription_paused, subscription_expired, customer_created, customer_updated.
Table 4: Discord_Verifications
| Field Name | Field Type | Notes |
|---|---|---|
| verification_id | Text | Primary field, unique identifier |
| subscription_id | Text | Chargebee subscription ID |
| customer_id | Text | Chargebee customer ID |
| plan_id | Text | Plan ID at verification time |
| discord_id | Text | Discord user ID |
| discord_username | Text | Discord username |
| discord_email | Discord email from OAuth | |
| role_id | Text | Role ID assigned |
| role_name | Text | Role name |
| verification_status | Single Select | Options: pending, verified, failed, revoked, expired |
| verification_source | Single Select | Options: oauth, webhook, manual_support |
| verification_reason | Long Text | Notes about outcome |
| role_assigned | Checkbox | Must be Checkbox type |
| verified_at | Date Time | When verified |
| revoked_at | Date Time | When revoked |
| role_assigned_at | Date Time | When role assigned |
3.3 Get Airtable Credentials
- Create a Personal Access Token in Account → Developer Hub with scopes:
data.records:read,data.records:write, andschema.bases:read. - Copy the token → Save as env variable:
AIRTABLE_API_KEY - Copy your Base ID (format
appXXXXXXXXXXXXXX) from the Airtable URL → Save as env variable:AIRTABLE_BASE_ID
Step 4: Environment Variables
Create a .env file with the following values:
# Discord OAuth Configuration
DISCORD_CLIENT_ID=your_discord_client_id
DISCORD_CLIENT_SECRET=your_discord_client_secret
DISCORD_REDIRECT_URI=https://auth.stockedup.university/auth/discord/callback
# Discord Bot Configuration
DISCORD_BOT_TOKEN=your_discord_bot_token
DISCORD_GUILD_ID=your_discord_server_id
DISCORD_ROLE_ID=your_subscriber_role_id
# DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/xxx/xxx
# Discord Product Configuration
DISCORD_PRODUCT_PLAN_IDS=plan_discord_monthly,plan_discord_yearly
# Chargebee Configuration
CHARGEBEE_SITE=your_chargebee_site
CHARGEBEE_API_KEY=your_chargebee_api_key
CHARGEBEE_WEBHOOK_SECRET=your_chargebee_webhook_secret
# Airtable Configuration
AIRTABLE_API_KEY=your_airtable_personal_access_token
AIRTABLE_BASE_ID=appXXXXXXXXXXXXXX
AIRTABLE_TABLE_NAME=Subscriptions
AIRTABLE_CUSTOMERS_TABLE_NAME=Customers
AIRTABLE_AUDIT_LOGS_TABLE_NAME=Audit_Logs
AIRTABLE_DISCORD_VERIFICATIONS_TABLE_NAME=Discord_Verifications
AIRTABLE_AUDIT_LOG_RETENTION_DAYS=30
# Application Configuration
REDIRECT_BASE_URL=https://auth.stockedup.university
SESSION_SECRET=your-secure-random-string-at-least-32-characters-long
PORT=3000
BOT_PORT=3001
NODE_ENV=production
Environment Variable Reference
| Variable | Required? | Description |
|---|---|---|
| DISCORD_CLIENT_ID | Yes | Discord OAuth Client ID |
| DISCORD_CLIENT_SECRET | Yes | Discord OAuth Client Secret |
| DISCORD_REDIRECT_URI | Yes | OAuth callback URL |
| DISCORD_BOT_TOKEN | Yes | Bot token for gateway connection |
| DISCORD_GUILD_ID | Yes | Target Discord server ID |
| DISCORD_ROLE_ID | Yes | Role to assign |
| DISCORD_WEBHOOK_URL | No | Webhook for logging (optional) |
| DISCORD_PRODUCT_PLAN_IDS | Yes | Comma-separated Chargebee plan IDs |
| CHARGEBEE_SITE | Yes | Chargebee site name |
| CHARGEBEE_API_KEY | Yes | Chargebee API key |
| CHARGEBEE_WEBHOOK_SECRET | Yes | Webhook verification secret (required for authentication) |
| AIRTABLE_API_KEY | Yes | Airtable Personal Access Token |
| AIRTABLE_BASE_ID | Yes | Airtable base ID |
| AIRTABLE_TABLE_NAME | Yes | Subscriptions table name |
| AIRTABLE_CUSTOMERS_TABLE_NAME | No | Customers table (default: Customers) |
| AIRTABLE_AUDIT_LOGS_TABLE_NAME | No | Audit logs table (default: Audit_Logs) |
| AIRTABLE_DISCORD_VERIFICATIONS_TABLE_NAME | No | Verifications table (default: Discord_Verifications) |
| AIRTABLE_AUDIT_LOG_RETENTION_DAYS | No | Retention (days, 0 = indefinite) |
| REDIRECT_BASE_URL | Yes | Base URL for redirects |
| SESSION_SECRET | Yes | Secret for signing OAuth state (min 32 chars) |
| PORT | No | Middleware port (default: 3000) |
| BOT_PORT | No | Bot internal API port (default: 3001) |
| NODE_ENV | No | Environment (default: development) |
Step 5: Verification and Testing
5.1 Health Check
curl https://auth.stockedup.university/health
# Expected: {"status":"ok","service":"middleware","timestamp":"..."}
5.2 Test Webhook
- Use Chargebee's "Test Webhook".
- Verify the event in application logs and in the Airtable Audit_Logs table.
5.3 Test OAuth Flow
- Create a test subscription in Chargebee and complete the payment.
- Confirm the redirect to Discord OAuth and authorize.
- Check Airtable for updated Subscriptions and Discord_Verifications records plus Audit_Logs entries.
5.4 Verify Role Assignment
- Confirm the user receives the role in Discord.
- If the user joins after purchase, the
guildMemberAddevent should assign the role automatically.
Step 6: Build and Deploy (PebbleHost)
6.1 Prepare Project Files
Ensure your local project has the following core files and folders:
stockedup-discord-subscription-automation/
├── apps/
│ ├── discord-bot/
│ │ ├── dist/ ← Compiled JavaScript (from build)
│ │ ├── src/ ← TypeScript source
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── middleware/
│ ├── dist/ ← Compiled JavaScript (from build)
│ ├── src/ ← TypeScript source
│ ├── package.json
│ └── tsconfig.json
├── packages/
│ ├── shared-airtable/
│ │ ├── dist/ ← Compiled JavaScript
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── shared-config/
│ │ ├── dist/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── shared-logger/
│ │ ├── dist/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── shared-types/
│ ├── dist/
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── docs/ ← Documentation
├── scripts/ ← Utility scripts
├── .env ← Create with your environment variables
├── .env.example ← Template
├── bot.js ← Main entry point
├── package.json
├── package-lock.json
├── tsconfig.json
└── README.md
dist/ folders contain compiled JavaScript and are essential for production. Run npm run build locally before uploading, or be prepared to build on the server.
6.2 Upload to PebbleHost
Upload all project files to your PebbleHost server. You can use:
- SFTP/FTP: Use FileZilla or similar to upload the entire project folder
- Git: Clone the repository directly on the server (recommended)
6.3 Create .env File
SSH into your PebbleHost server and create a .env file in the root directory with all environment variables from Step 4:
cd stockedup-discord-subscription-automation
nano .env # Or use vi, vim, or upload via SFTP
.env file is in the root directory and contains all required variables from Step 4.
6.4 Install Dependencies and Build
In your project directory on the server, run:
npm install
npm run build
This will install all dependencies and compile TypeScript to JavaScript in the dist/ folders.
6.5 Start the Application
PebbleHost will automatically start the bot using the bot.js entry point. If you need to manually start it:
node bot.js
The bot will automatically start both services:
- Middleware: Runs on port 3000 (handles OAuth and webhooks)
- Discord Bot: Runs on port 3001 (handles role assignments)
6.6 Verify Startup
Check the console logs to confirm both services started successfully. You should see:
[Middleware] Server listening on port 3000
[Discord Bot] Bot logged in as YourBotName#1234
[Discord Bot] Server listening on port 3001
bot.js as the main entry point and will automatically restart it if it crashes. No need for PM2 or additional process managers.
6.7 Post-Deployment Verification
After deployment, verify everything is working:
- Health check:
curl https://auth.stockedup.university/health - Test webhook: Use Chargebee's test webhook feature
- Test OAuth: Create a test subscription and complete the flow
- Monitor logs: Watch for any errors in Airtable Audit_Logs and console output
Troubleshooting
Common Issues
- Missing required environment variable: Ensure all required variables are set (see Step 4).
- Chargebee webhook signature verification failed: Verify
CHARGEBEE_WEBHOOK_SECRETand webhook URL. - Airtable field errors: Use Checkbox type for
role_assigned; ensure Single Select options match exactly. - Bot cannot assign roles: Check role hierarchy, permissions,
DISCORD_GUILD_ID, andDISCORD_ROLE_ID. - OAuth redirect errors: Ensure
DISCORD_REDIRECT_URImatches Discord settings and SSL is valid; confirmREDIRECT_BASE_URL. - Rate limiting: Airtable (5 req/s) and Discord OAuth (20 req/15min/IP). The system includes internal backoff.
Log Locations
- Application logs: stdout/stderr or PM2 logs.
- Audit logs: Airtable Audit_Logs table.
- Discord webhook logs: Configure
DISCORD_WEBHOOK_URL.
Getting Help
- Check the Airtable Audit_Logs table.
- Review console output.
- Verify environment variables.
- Contact support at support@stockedup.university.
Optional: Additional Features
These features are optional but can enhance monitoring and debugging capabilities.
Discord Webhook Logs
Configure a Discord webhook to receive real-time application logs and notifications in a Discord channel.
Benefits:
- Real-time notifications for subscription events
- Role assignment confirmations
- Error alerts and debugging information
- Centralized monitoring in your Discord server
Setup Instructions:
- Go to your Discord server and select the channel where you want to receive logs (e.g., #bot-logs or #notifications).
- Click the gear icon (⚙️) next to the channel name to open Channel Settings.
- Navigate to Integrations → Webhooks.
- Click New Webhook or Create Webhook.
- Give it a name (e.g., "StockedUp Bot Logs").
- Optionally customize the webhook avatar to match your branding.
- Click Copy Webhook URL.
- Save as env variable (optional):
DISCORD_WEBHOOK_URL - Add the URL to your
.envfile and restart the application.
Optional: Cloudflare Tunnel Setup
Use Cloudflare Tunnel to expose your production server securely without opening ports or managing SSL certificates.
Install cloudflared
# macOS
brew install cloudflared
# Linux
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/
# Windows
winget install Cloudflare.cloudflared
Authenticate
cloudflared tunnel login
Create a Tunnel
cloudflared tunnel create stockedup
Save the tunnel ID and credentials file path.
Configure DNS
cloudflared tunnel route dns stockedup your-subdomain.yourdomain.com
Create Configuration File
tunnel: YOUR_TUNNEL_ID
credentials-file: /path/to/credentials.json
ingress:
- hostname: your-subdomain.yourdomain.com
service: http://localhost:3000
- service: http_status:404
Run the Tunnel
cloudflared tunnel run stockedup
Set up cloudflared as a system service for production.
Quick Reference
Active Endpoints
| Endpoint | Method | Description |
|---|---|---|
| /health | GET | Health check endpoint |
| /chargebee/webhook | POST | Receive Chargebee webhooks |
| /auth/discord | GET | Discord onboarding page (Chargebee redirect target) |
| /auth/discord/start | GET | Initiate Discord OAuth flow |
| /auth/discord/callback | GET | Discord OAuth callback handler |
| /auth/discord/retry-role | POST | Manual retry for role assignment |
| /internal/cleanup-audit-logs | POST | Maintenance endpoint for log cleanup |
https://auth.stockedup.university/auth/discord?subscription_id={{subscription.id}}&customer_id={{customer.id}}&plan_id={{plan.id}}
Commands
# Development (with Cloudflare tunnel)
npm run dev
# Development (without tunnel)
npm run dev:no-tunnel
# Production
npm run start
# Build
npm run build
# Lint
npm run lint