StockedUp Discord Subscription Automation

Production Deployment Guide

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

ComponentDescriptionPort
Middleware ServiceHandles Chargebee redirects, OAuth, and webhooks3000
Discord Bot ServiceDiscord Gateway connection and role management3001

Table of Contents

Prerequisites

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

  1. Go to the Discord Developer Portal.
  2. Create a new application (e.g., "StockedUp Subscription Bot").
  3. Copy the Client IDSave as env variable: DISCORD_CLIENT_ID
  4. Copy the Client SecretSave as env variable: DISCORD_CLIENT_SECRET

1.2 Configure OAuth2

  1. 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

  1. In the Bot section, click Add Bot and reset the token.
  2. Copy the token → Save as env variable: DISCORD_BOT_TOKEN
⚠️ The bot token is shown only once—store it securely.

1.4 Configure Bot Permissions

1.5 Invite the Bot

  1. Use OAuth2 → URL Generator with scope bot.
  2. Select permissions Manage Roles, View Channels, and Send Messages.
  3. Open the generated URL to authorize the bot in your server.

1.6 Get Server and Role IDs

  1. Enable Discord Developer Mode.
  2. Right-click your server → Copy ID → Save as env variable: DISCORD_GUILD_ID
  3. Right-click the role to assign → Copy ID → Save as env variable: DISCORD_ROLE_ID
⚠️ The bot’s role must be higher than the role it assigns.

Step 2: Chargebee Configuration

2.1 Get API Credentials

  1. In Settings → Configure Chargebee → API Keys:
  2. Copy your site name (subdomain only, e.g., stockedup-test) → Save as env variable: CHARGEBEE_SITE
  3. Copy your API key → Save as env variable: CHARGEBEE_API_KEY

2.2 Configure Checkout Redirect URL

  1. 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)

  1. In Settings → Configure Chargebee → Webhooks, click Add Webhook.
  2. Set the Webhook URL to: https://auth.stockedup.university/chargebee/webhook
  3. Enable "Protect webhook URL with basic authentication"
  4. Generate or create a secure webhook secret (minimum 32 characters).
  5. In the Username field, paste your webhook secret (leave Password field empty).
  6. Save as env variable: CHARGEBEE_WEBHOOK_SECRET
  7. Select API Version 2
  8. Recommended: Select specific events (more efficient than "All Events"):
    • subscription_created
    • subscription_changed
    • subscription_cancelled
    • subscription_paused
    • subscription_resumed
    • subscription_renewed
    • subscription_deleted
    • customer_created
    • customer_changed
    • customer_deleted
  9. Click Create to save the webhook.
⚠️ Important: Keep your webhook secret secure. You've configured it as the Username in Chargebee's Basic Auth, and you'll also need it as the CHARGEBEE_WEBHOOK_SECRET environment variable in Step 4.

2.4 Identify Discord Product Plan IDs

  1. In Product Catalog → Plans, collect the plan IDs that should trigger Discord roles.
  2. List them comma-separated → Save as env variable: DISCORD_PRODUCT_PLAN_IDS
  3. 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 NameField TypeNotes
subscription_idTextPrimary field, unique identifier
customer_idTextChargebee customer ID
plan_idTextChargebee plan ID
plan_nameTextFriendly plan name
statusSingle SelectOptions: active, cancelled, paused, expired
period_startDate TimeCurrent period start
period_endDate TimeCurrent period end/renewal date
active_discord_idTextDiscord user ID
active_discord_usernameTextDiscord username
active_discord_emailEmailDiscord email
active_role_idTextDiscord role ID
active_role_nameTextRole name
role_assignedCheckboxMust be Checkbox type
role_assigned_atDate TimeWhen role was assigned
last_verified_atDate TimeLast verification timestamp
auth_url_tokenTextEncrypted token for staff sharing
discord_auth_urlURLFull Discord auth URL

Table 2: Customers

Field NameField TypeNotes
customer_idTextPrimary field, Chargebee customer ID
emailEmailCustomer email
first_nameTextCustomer first name
last_nameTextCustomer last name

Table 3: Audit_Logs

Field NameField TypeNotes
log_idAutonumberAuto-generated
subscription_idTextAssociated subscription
customer_idTextAssociated customer
discord_idTextDiscord user ID
verification_idTextLink to verification record
eventSingle SelectSee event types below
statusSingle SelectOptions: success, failure
messageLong TextHuman-readable message
error_detailsLong TextTechnical error details
ip_addressTextClient IP
user_agentTextClient user agent
serviceSingle SelectOptions: 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 NameField TypeNotes
verification_idTextPrimary field, unique identifier
subscription_idTextChargebee subscription ID
customer_idTextChargebee customer ID
plan_idTextPlan ID at verification time
discord_idTextDiscord user ID
discord_usernameTextDiscord username
discord_emailEmailDiscord email from OAuth
role_idTextRole ID assigned
role_nameTextRole name
verification_statusSingle SelectOptions: pending, verified, failed, revoked, expired
verification_sourceSingle SelectOptions: oauth, webhook, manual_support
verification_reasonLong TextNotes about outcome
role_assignedCheckboxMust be Checkbox type
verified_atDate TimeWhen verified
revoked_atDate TimeWhen revoked
role_assigned_atDate TimeWhen role assigned

3.3 Get Airtable Credentials

  1. Create a Personal Access Token in Account → Developer Hub with scopes: data.records:read, data.records:write, and schema.bases:read.
  2. Copy the token → Save as env variable: AIRTABLE_API_KEY
  3. 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

VariableRequired?Description
DISCORD_CLIENT_IDYesDiscord OAuth Client ID
DISCORD_CLIENT_SECRETYesDiscord OAuth Client Secret
DISCORD_REDIRECT_URIYesOAuth callback URL
DISCORD_BOT_TOKENYesBot token for gateway connection
DISCORD_GUILD_IDYesTarget Discord server ID
DISCORD_ROLE_IDYesRole to assign
DISCORD_WEBHOOK_URLNoWebhook for logging (optional)
DISCORD_PRODUCT_PLAN_IDSYesComma-separated Chargebee plan IDs
CHARGEBEE_SITEYesChargebee site name
CHARGEBEE_API_KEYYesChargebee API key
CHARGEBEE_WEBHOOK_SECRETYesWebhook verification secret (required for authentication)
AIRTABLE_API_KEYYesAirtable Personal Access Token
AIRTABLE_BASE_IDYesAirtable base ID
AIRTABLE_TABLE_NAMEYesSubscriptions table name
AIRTABLE_CUSTOMERS_TABLE_NAMENoCustomers table (default: Customers)
AIRTABLE_AUDIT_LOGS_TABLE_NAMENoAudit logs table (default: Audit_Logs)
AIRTABLE_DISCORD_VERIFICATIONS_TABLE_NAMENoVerifications table (default: Discord_Verifications)
AIRTABLE_AUDIT_LOG_RETENTION_DAYSNoRetention (days, 0 = indefinite)
REDIRECT_BASE_URLYesBase URL for redirects
SESSION_SECRETYesSecret for signing OAuth state (min 32 chars)
PORTNoMiddleware port (default: 3000)
BOT_PORTNoBot internal API port (default: 3001)
NODE_ENVNoEnvironment (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

  1. Use Chargebee's "Test Webhook".
  2. Verify the event in application logs and in the Airtable Audit_Logs table.

5.3 Test OAuth Flow

  1. Create a test subscription in Chargebee and complete the payment.
  2. Confirm the redirect to Discord OAuth and authorize.
  3. Check Airtable for updated Subscriptions and Discord_Verifications records plus Audit_Logs entries.

5.4 Verify Role Assignment

  1. Confirm the user receives the role in Discord.
  2. If the user joins after purchase, the guildMemberAdd event should assign the role automatically.

Step 6: Build and Deploy (PebbleHost)

💡 Important: Complete all previous steps before deploying. Ensure all credentials, webhooks, and configurations are in place.

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
⚠️ Critical: The 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:

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
⚠️ Critical: Ensure your .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:

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
💡 PebbleHost Auto-Start: PebbleHost detects 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:

  1. Health check: curl https://auth.stockedup.university/health
  2. Test webhook: Use Chargebee's test webhook feature
  3. Test OAuth: Create a test subscription and complete the flow
  4. Monitor logs: Watch for any errors in Airtable Audit_Logs and console output

Troubleshooting

Common Issues

Log Locations

Getting Help

  1. Check the Airtable Audit_Logs table.
  2. Review console output.
  3. Verify environment variables.
  4. Contact support at [email protected].

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:

Setup Instructions:

  1. Go to your Discord server and select the channel where you want to receive logs (e.g., #bot-logs or #notifications).
  2. Click the gear icon (⚙️) next to the channel name to open Channel Settings.
  3. Navigate to IntegrationsWebhooks.
  4. Click New Webhook or Create Webhook.
  5. Give it a name (e.g., "StockedUp Bot Logs").
  6. Optionally customize the webhook avatar to match your branding.
  7. Click Copy Webhook URL.
  8. Save as env variable (optional): DISCORD_WEBHOOK_URL
  9. Add the URL to your .env file and restart the application.
💡 Tip: Create a dedicated private channel for logs to keep your main channels clean. Set appropriate permissions so only admins/moderators can see the logs.

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

EndpointMethodDescription
/healthGETHealth check endpoint
/chargebee/webhookPOSTReceive Chargebee webhooks
/auth/discordGETDiscord onboarding page (Chargebee redirect target)
/auth/discord/startGETInitiate Discord OAuth flow
/auth/discord/callbackGETDiscord OAuth callback handler
/auth/discord/retry-rolePOSTManual retry for role assignment
/internal/cleanup-audit-logsPOSTMaintenance endpoint for log cleanup
📍 Chargebee Redirect URL Format:
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