- Go to https://app.dodopayments.com → log in
- Navigate to Products (left sidebar)
- Click + Add Product (or "Create Product")
- Fill in:
- Product Name:
Talkamore Monthly - Type: Select Subscription (not one-time)
- Billing Interval: Monthly
- Price:
12.99 USD - Description (optional): "Talkamore Pro — monthly subscription. Cancel anytime."
- Product Name:
- Click Save / Create
- Copy the product ID — it looks like
pdt_xxxxxxxxxxxxx - Save this as your
DODO_MONTHLY_PRODUCT_ID
- Still in Products, click + Add Product again
- Fill in:
- Product Name:
Talkamore Yearly - Type: Select Subscription
- Billing Interval: Yearly (or Annual)
- Price:
109.00 USD - Description (optional): "Talkamore Pro — yearly subscription. Save 30% vs monthly."
- Product Name:
- Click Save / Create
- Copy the product ID —
pdt_xxxxxxxxxxxxx - Save this as your
DODO_YEARLY_PRODUCT_ID
-
Go to Webhooks (left sidebar, or under Settings → Webhooks)
-
You should already have a webhook configured pointing at your backend:
https://your-backend-url.railway.app/api/webhooks/dodo -
Verify it's still active and the signing secret matches your
DODO_WEBHOOK_SECRETenv var -
If you need to check the secret: the webhook detail page shows the signing secret (or lets you regenerate it). It starts with
whsec_ -
Check which events are enabled. You need ALL of these:
payment.succeeded← already enabledpayment.failed← already enabledsubscription.active(orsubscription.created) ← NEW, enable thissubscription.renewed← NEW, enable thissubscription.cancelled← NEW, enable thissubscription.expired← NEW, enable this
If Dodo doesn't let you pick individual events, it likely sends all events to the endpoint — that's fine, your backend handler will route by
typefield.
If you had a FAM10 coupon for lifetime and want it for subscriptions:
- Go to Coupons / Discounts in the dashboard
- Create a new coupon:
- Code:
FAM10 - Type: Percentage discount
- Amount: 10%
- Applicable products: Select both Talkamore Monthly and Talkamore Yearly
- Usage limits: Set as desired (e.g., first 100 uses, or unlimited)
- Code:
- Save
If you're not keeping the coupon for subscriptions, skip this step.
Go to your Railway project → backend service → Variables tab.
Add these two new variables:
DODO_MONTHLY_PRODUCT_ID=pdt_xxxxxxxxxxxxx (from Step 1)
DODO_YEARLY_PRODUCT_ID=pdt_xxxxxxxxxxxxx (from Step 2)
Keep these existing variables as-is:
DODO_API_KEY=... (unchanged)
DODO_WEBHOOK_SECRET=whsec_... (unchanged)
DODO_API_BASE_URL=... (unchanged)
DODO_PRODUCT_ID=pdt_... (keep for ~7 days for in-flight lifetime purchases, then remove)
Before going live, test everything in Dodo's test mode:
-
In the Dodo dashboard, switch to Test Mode (usually a toggle in the top bar or settings)
-
Create the same two products in test mode (test product IDs will be different)
-
Set your staging/dev env vars to use the test product IDs
-
Set
DODO_API_BASE_URL=https://test.dodopayments.comon your staging environment -
Use Dodo's test card numbers to simulate:
- Successful payment: Complete a monthly and yearly checkout
- Failed payment: Use a test card that declines
- Cancellation: Cancel a test subscription from the dashboard and verify the webhook fires
- Renewal: If Dodo allows simulating renewals in test mode, trigger one
-
Check your webhook logs in the Dodo dashboard → Webhooks → Recent deliveries:
- Verify events arrive with the correct
typefield - Verify
metadata.userIdis present in subscription events - Verify the response from your backend is
200
- Verify events arrive with the correct
Once testing is complete:
- Switch Railway env vars to live product IDs
- Ensure
DODO_API_BASE_URLishttps://live.dodopayments.com(or remove it — that's the default) - Deploy backend first (so webhook handlers are ready)
- Deploy frontend (so the new pricing UI goes live)
- Do one real checkout yourself to verify end-to-end
After a week with no lifetime checkouts in-flight:
- In Dodo dashboard: Archive or Deactivate the old lifetime product (don't delete — you may want the history)
- Remove
DODO_PRODUCT_IDenv var from Railway - Remove the legacy
payment.succeeded → lifetimeAccess = truebranch from the backend webhook handler
- Don't delete the lifetime product — just archive it. Existing purchase records reference it.
- Webhook event type names: The exact strings Dodo uses may vary. Check your dashboard's webhook log after a test purchase to see the actual
typevalues in the JSON payload. Common patterns:subscription.active,subscription.created,subscription_created— confirm which one Dodo actually sends. - The signing secret doesn't change when you add subscription products — it's per-webhook-endpoint, not per-product.
- Dodo retries failed webhook deliveries — your backend must be idempotent (it already is for lifetime via the P2002 catch). Apply the same pattern for subscription events.