Stripe creating subscription: Unauthorized 401

Hello, I'm running into an issue with Stripe Subscriptions whereas I cannot generate a new subscription for an user. I'm using @better-auth/strip and receive a 401 Unauthorized when I'm creating a subscription on the client. The only weird thing AFAIK is that I use the convexAdapter, so I cannot run migrations for stripe myself (but this seems unrelated to the unauthorized). I've checked the .env.local keys a dozen times. I'm running a Tanstack Start app. auth.tsx
import { betterAuth } from "better-auth";
import { APP_NAME, DEFAULT_AVATAR, DEFAULT_LANGUAGE } from "@/config/constants";
import { ConvexHttpClient } from "convex/browser";
import { convexAdapter } from "@better-auth-kit/convex";
import { stripe } from "@better-auth/stripe";
import Stripe from "stripe";

const convexClient = new ConvexHttpClient(import.meta.env.VITE_CONVEX_URL);
const stripeClient = new Stripe(import.meta.env.VITE_STRIPE_SECRET_KEY);

export const AUTH_PROVIDERS = {
CREDENTIAL: "credential",
APPLE: "apple",
GOOGLE: "google",
} as const;

export const auth = betterAuth({
appName: APP_NAME,
secret: import.meta.env.VITE_BETTER_AUTH_SECRET,
baseURL: import.meta.env.VITE_BETTER_AUTH_URL,
database: convexAdapter(convexClient),
plugins: [
stripe({
stripeClient,
stripeWebhookSecret: import.meta.env.VITE_STRIPE_WEBHOOKS_ENDPOINT_SECRET,
createCustomerOnSignUp: true,
subscription: {
enabled: true,
plans: [
{
name: "monthly", // the name of the plan, it'll be automatically lower cased when stored in the database
priceId: import.meta.env.VITE_STRIPE_MONTHLY_PRICE_ID, // the price id from stripe
},
{
name: "yearly",
priceId: import.meta.env.VITE_STRIPE_YEARLY_PRICE_ID,
},
],
},
}),
],
import { betterAuth } from "better-auth";
import { APP_NAME, DEFAULT_AVATAR, DEFAULT_LANGUAGE } from "@/config/constants";
import { ConvexHttpClient } from "convex/browser";
import { convexAdapter } from "@better-auth-kit/convex";
import { stripe } from "@better-auth/stripe";
import Stripe from "stripe";

const convexClient = new ConvexHttpClient(import.meta.env.VITE_CONVEX_URL);
const stripeClient = new Stripe(import.meta.env.VITE_STRIPE_SECRET_KEY);

export const AUTH_PROVIDERS = {
CREDENTIAL: "credential",
APPLE: "apple",
GOOGLE: "google",
} as const;

export const auth = betterAuth({
appName: APP_NAME,
secret: import.meta.env.VITE_BETTER_AUTH_SECRET,
baseURL: import.meta.env.VITE_BETTER_AUTH_URL,
database: convexAdapter(convexClient),
plugins: [
stripe({
stripeClient,
stripeWebhookSecret: import.meta.env.VITE_STRIPE_WEBHOOKS_ENDPOINT_SECRET,
createCustomerOnSignUp: true,
subscription: {
enabled: true,
plans: [
{
name: "monthly", // the name of the plan, it'll be automatically lower cased when stored in the database
priceId: import.meta.env.VITE_STRIPE_MONTHLY_PRICE_ID, // the price id from stripe
},
{
name: "yearly",
priceId: import.meta.env.VITE_STRIPE_YEARLY_PRICE_ID,
},
],
},
}),
],
Solution:
```typescript subscription: defineTable({ id: v.string(), // <-- this was the problem plan: v.string(), referenceId: v.id("user"),...
Jump to solution
28 Replies
RadiantFrog
RadiantFrogOP•2mo ago
auth-client.ts
export const { signIn, signUp, deleteUser, signOut, useSession, subscription } = createAuthClient({
baseURL: import.meta.env.VITE_BETTER_AUTH_URL,
plugins: [
inferAdditionalFields<typeof auth>(),
stripeClient({
subscription: true, //if you want to enable subscription management
}),
],
});
export const { signIn, signUp, deleteUser, signOut, useSession, subscription } = createAuthClient({
baseURL: import.meta.env.VITE_BETTER_AUTH_URL,
plugins: [
inferAdditionalFields<typeof auth>(),
stripeClient({
subscription: true, //if you want to enable subscription management
}),
],
});
Client code:
const createStripeCheckoutSession = async ({
userId,
isYearly,
}: { userId: string; isYearly: boolean }) => {
setIsPending(true);
const { error } = await subscription.upgrade({
referenceId: userId,
plan: isYearly ? "yearly" : "monthly",
successUrl: "/dashboard?subscriptionSuccess=true",
cancelUrl: "/dashboard?subscriptionCanceled=true",
});

if (error) {
showNotification({
type: "error",
message: "Error creating checkout session",
description: error.message,
});
console.error("Error creating checkout session:", error);
}
setIsPending(false);
};
const createStripeCheckoutSession = async ({
userId,
isYearly,
}: { userId: string; isYearly: boolean }) => {
setIsPending(true);
const { error } = await subscription.upgrade({
referenceId: userId,
plan: isYearly ? "yearly" : "monthly",
successUrl: "/dashboard?subscriptionSuccess=true",
cancelUrl: "/dashboard?subscriptionCanceled=true",
});

if (error) {
showNotification({
type: "error",
message: "Error creating checkout session",
description: error.message,
});
console.error("Error creating checkout session:", error);
}
setIsPending(false);
};
Ping
Ping•2mo ago
Hey, are you sure you're logged before creating a subscription?
RadiantFrog
RadiantFrogOP•2mo ago
Hey @Ping, yes I'm logged into my user with better-auth
Ping
Ping•2mo ago
Does the error give you any other details besides that it's "Unauthorized 401"?
RadiantFrog
RadiantFrogOP•2mo ago
This is the entire error
Pro.tsx:56
POST https://dev.progressmade.ai/api/auth/subscription/upgrade 401 (Unauthorized)
Pro.tsx:69 Error creating checkout session:
{code: 'UNAUTHORIZED', message: 'Unauthorized', status: 401, statusText: ''}
Pro.tsx:56
POST https://dev.progressmade.ai/api/auth/subscription/upgrade 401 (Unauthorized)
Pro.tsx:69 Error creating checkout session:
{code: 'UNAUTHORIZED', message: 'Unauthorized', status: 401, statusText: ''}
I dont think it has to do with the convex adapter
Ping
Ping•2mo ago
Yeah, it's very unlikely. :) By the way, I'm the creator of the Convex adapter, if you run into any issues don't hesitate to just ping me :)
RadiantFrog
RadiantFrogOP•2mo ago
haha nice 🙂 I know 🙂
Ping
Ping•2mo ago
Can you test without passing referenceId in your subscription.upgrade call?
RadiantFrog
RadiantFrogOP•2mo ago
ah the error changed to a 500
# SERVER_ERROR: Error: [Request ID: 91870f8125d70b9e] Server Error
Uncaught Error: Error {} is not a supported Convex type.
at convexToJsonInternal (../../node_modules/.pnpm/convex@1.22.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/convex/src/values/value.ts:339:6)
at convexToJson (../../node_modules/.pnpm/convex@1.22.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/convex/src/values/value.ts:417:0)
# SERVER_ERROR: Error: [Request ID: 91870f8125d70b9e] Server Error
Uncaught Error: Error {} is not a supported Convex type.
at convexToJsonInternal (../../node_modules/.pnpm/convex@1.22.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/convex/src/values/value.ts:339:6)
at convexToJson (../../node_modules/.pnpm/convex@1.22.0_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/convex/src/values/value.ts:417:0)
Ping
Ping•2mo ago
Maybe there's an error in your logs now? 👀 Well! That sure seems more like a Convex adapter issue xD
RadiantFrog
RadiantFrogOP•2mo ago
yep XD I would've been perfect if it was plug and play 😛
Ping
Ping•2mo ago
Can you enable debug logs from the convex adapter options?
RadiantFrog
RadiantFrogOP•2mo ago
[convex-adapter] findOne result {
result: {
avatar: 'optimistic',
email: 'brady.upton41@yahoo.com',
emailVerified: false,
language: 'en',
name: 'Ora Sipes',
plan: 'free',
proStatus: 'inactive',
providerId: 'credential',
stripeCustomerId: 'cus_S4P07Pv937bQE7',
updatedAt: 2025-04-04T19:46:52.787Z,
createdAt: 2025-04-04T19:46:53.136Z,
id: 'jh7fbj364wvp9vg09jzwrx6d5n7dc7hb'
},
duration: '398ms'
}
[convex-adapter] findMany {
model: 'subscription',
where: [
{ field: 'referenceId', value: 'jh7fbj364wvp9vg09jzwrx6d5n7dc7hb' }
],
limit: undefined,
offset: undefined,
sortBy: undefined
}
[convex-adapter] findMany queryString {
queryString: 'q.eq(q.field("referenceId"), "jh7fbj364wvp9vg09jzwrx6d5n7dc7hb")'
}
[convex-adapter] findMany result { result: [], duration: '164ms' }
[convex-adapter] create {
model: 'subscription',
values: {
plan: 'yearly',
stripeCustomerId: 'cus_S4P07Pv937bQE7',
status: 'incomplete',
referenceId: 'jh7fbj364wvp9vg09jzwrx6d5n7dc7hb',
seats: 1
},
select: undefined
}
# SERVER_ERROR: Error: [Request ID: 69a0ed3810ddc610] Server Error
Uncaught Error: Error {} is not a supported Convex type.
[convex-adapter] findOne result {
result: {
avatar: 'optimistic',
email: 'brady.upton41@yahoo.com',
emailVerified: false,
language: 'en',
name: 'Ora Sipes',
plan: 'free',
proStatus: 'inactive',
providerId: 'credential',
stripeCustomerId: 'cus_S4P07Pv937bQE7',
updatedAt: 2025-04-04T19:46:52.787Z,
createdAt: 2025-04-04T19:46:53.136Z,
id: 'jh7fbj364wvp9vg09jzwrx6d5n7dc7hb'
},
duration: '398ms'
}
[convex-adapter] findMany {
model: 'subscription',
where: [
{ field: 'referenceId', value: 'jh7fbj364wvp9vg09jzwrx6d5n7dc7hb' }
],
limit: undefined,
offset: undefined,
sortBy: undefined
}
[convex-adapter] findMany queryString {
queryString: 'q.eq(q.field("referenceId"), "jh7fbj364wvp9vg09jzwrx6d5n7dc7hb")'
}
[convex-adapter] findMany result { result: [], duration: '164ms' }
[convex-adapter] create {
model: 'subscription',
values: {
plan: 'yearly',
stripeCustomerId: 'cus_S4P07Pv937bQE7',
status: 'incomplete',
referenceId: 'jh7fbj364wvp9vg09jzwrx6d5n7dc7hb',
seats: 1
},
select: undefined
}
# SERVER_ERROR: Error: [Request ID: 69a0ed3810ddc610] Server Error
Uncaught Error: Error {} is not a supported Convex type.
the subscription table is not getting new entries
Ping
Ping•2mo ago
Hmm. This.. is very strange.
RadiantFrog
RadiantFrogOP•2mo ago
yes, the create is in the logging but the table remains empty
Ping
Ping•2mo ago
The data which the create log shows seem to be some very basic data too, nohing complex or anything... Makes no sense why it would break...
RadiantFrog
RadiantFrogOP•2mo ago
No rush, I can log it on github if you want
Ping
Ping•2mo ago
Yeah please create an issue on my repo I think I'll have to reproduce a demo and test myself. I'll test this when I wake, it's 6am rn 😅
RadiantFrog
RadiantFrogOP•2mo ago
GitHub
Convex Adapter and Stripe plugin not working: Error {} is not a sup...
I'm receiving the following error when creating a Stripe subscription: # SERVER_ERROR: Error: [Request ID: 91870f8125d70b9e] Server Error Uncaught Error: Error {} is not a supported Convex type...
RadiantFrog
RadiantFrogOP•2mo ago
Thanks for the help!
4/4/2025, 10:07:30 PM [CONVEX M(betterAuth:insert)] Uncaught Error: Failed to insert or update a document in table "subscription" because it does not match the schema: Object is missing the required field `id`. Consider wrapping the field validator in `v.optional(...)` if this is expected.
4/4/2025, 10:07:30 PM [CONVEX M(betterAuth:insert)] Uncaught Error: Failed to insert or update a document in table "subscription" because it does not match the schema: Object is missing the required field `id`. Consider wrapping the field validator in `v.optional(...)` if this is expected.
Im just seeing this error logging right now in the
convex dex
convex dex
Ping
Ping•2mo ago
Do you have a schema defined for subscription?
RadiantFrog
RadiantFrogOP•2mo ago
yes hold on, I'm just checking it yup it works.
RadiantFrog
RadiantFrogOP•2mo ago
No description
RadiantFrog
RadiantFrogOP•2mo ago
without the schema
Solution
RadiantFrog
RadiantFrog•2mo ago
subscription: defineTable({
id: v.string(), // <-- this was the problem
plan: v.string(),
referenceId: v.id("user"),
stripeCustomerId: v.optional(v.string()),
stripeSubscriptionId: v.optional(v.string()),
status: v.string(),
periodStart: v.optional(v.string()),
periodEnd: v.optional(v.string()),
cancelAtPeriodEnd: v.optional(v.boolean()),
seats: v.optional(v.number()),
trialStart: v.optional(v.string()),
trialEnd: v.optional(v.string()),
}),
subscription: defineTable({
id: v.string(), // <-- this was the problem
plan: v.string(),
referenceId: v.id("user"),
stripeCustomerId: v.optional(v.string()),
stripeSubscriptionId: v.optional(v.string()),
status: v.string(),
periodStart: v.optional(v.string()),
periodEnd: v.optional(v.string()),
cancelAtPeriodEnd: v.optional(v.boolean()),
seats: v.optional(v.number()),
trialStart: v.optional(v.string()),
trialEnd: v.optional(v.string()),
}),
RadiantFrog
RadiantFrogOP•2mo ago
Sorry for the trouble. But now you know it works! convex adapter and stripe plugin work well, if you're not misconfiguring it ;O
Ping
Ping•2mo ago
No not your fault at all! This is actually my fault 👀 adapters are meant to provide an id during row creation if the data didn't already provide one.. When creating the convex adapter, it was a while ago while I was still somewhat new to BA as well... so there are definitely pitholes which I'm sure I didn't take into account. The good news is that all of those pitholes will be fixed from my PR in better-auth to introduce a createAdapter helper. Anyway, details aside, I'm glad it's solved!
RadiantFrog
RadiantFrogOP•2mo ago
Ah okay. Pretty amazing experience with integrating it for such a new library.

Did you find this page helpful?