AttendiBotAttendiBot

Billing Setup

Stripe products, webhooks, and environment variables for operators.

Self-hosting preview — Managed AttendiBot hosting is the recommended path today. Self-hosting support is coming soon; this guide is published for early adopters and operators preparing their infrastructure.

AttendiBot uses Stripe Checkout (subscriptions) and the Customer Portal. Configure these in the Stripe Dashboard (test mode first).

Products and prices

Create two products with recurring prices:

ProductMonthly priceAnnual price
AttendiBot Pro$6.00 / month$60.00 / year
AttendiBot Enterprise$18.00 / month$180.00 / year

Copy each Price ID (price_...) into the web environment variables below.

Customer Portal

Enable the Customer Portal in Stripe Dashboard → Settings → Billing → Customer portal:

  • Allow customers to update payment methods
  • Allow switching between all four prices (Pro/Enterprise × monthly/annual)
  • Allow cancellation (at period end recommended)
  • End active trials when customers switch plans
  • Prorate charges and credits on plan changes
  • Invoice prorations immediately at update time
  • Downgrades to a cheaper plan: at period end (recommended)
  • Switch to shorter interval (annual → monthly): update immediately

Customers with an active subscription should use Manage billing in the dashboard — not Upgrade, which is only for new subscriptions.

Webhook

Add endpoint: https://attendibot.com/api/billing/webhook

Events to subscribe:

  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.payment_failed

For local development:

stripe listen --forward-to localhost:3000/api/billing/webhook

Use the webhook signing secret from the CLI output as STRIPE_WEBHOOK_SECRET.

Environment variables (web)

Add to web/.env.local (dev) or deployment secrets (production):

STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_PRO_MONTHLY=price_...
STRIPE_PRICE_PRO_ANNUAL=price_...
STRIPE_PRICE_ENTERPRISE_MONTHLY=price_...
STRIPE_PRICE_ENTERPRISE_ANNUAL=price_...

ATTENDIBOT_API_INTERNAL_SECRET must match the backend API_INTERNAL_SECRET so webhooks can sync tiers.

Optional backend override

For staging or local testing without Stripe:

BILLING_ALLOW_MANUAL_TIER=1

When set, dashboard admins can still self-select paid tiers without checkout.

Monitoring

  • In Stripe Dashboard → Developers → Webhooks, enable email alerts for failed webhook deliveries
  • Check web service logs for [Stripe webhook] entries (signature failures return 400; sync failures return 500 and Stripe retries)
  • Verify stripe_webhook_events and guild_billing in PostgreSQL after test checkouts

Troubleshooting

Payment succeeded but tier did not update

  1. Stripe Dashboard → Webhooks → select endpoint → check recent delivery attempts (look for 500 responses)
  2. Confirm web can reach the backend (ATTENDIBOT_API_BASE_URL internal URL)
  3. Query SELECT * FROM stripe_webhook_events ORDER BY processed_at DESC LIMIT 10; — event should appear after successful sync
  4. Query SELECT * FROM guild_billing WHERE guild_id = ...; and guild_voice_settings.subscription_tier
  5. Replay the failed event from Stripe Dashboard if sync failed before marking processed

Customer has two Stripe subscriptions

Usually caused by clicking Upgrade while already subscribed. Cancel the duplicate in Stripe Dashboard and direct the customer to Manage billing for plan changes.

Unknown price ID in webhook logs

Verify all four STRIPE_PRICE_* env vars on the web service match live Stripe Price IDs. A mismatch causes webhooks to fail without downgrading the tier.

Manual recovery

If tier and Stripe are out of sync, update via Stripe Customer Portal (triggers customer.subscription.updated) or replay the webhook event from Stripe Dashboard.

Edit on GitHub

On this page