How is account.accountId auto-populated for Twitter (X) OAuth2? Is /2/users/me called internally?

Hi! I’d like to confirm the Twitter (X) OAuth2 behavior. Observation: Even without calling /2/users/me on my side, account.accountId is automatically saved with the X user ID (e.g., 777337382261497856). Question: Does the provider internally call /2/users/me via getUserInfo during the callback, and then persist data.id to account.accountId? Scopes: In socialProviders.twitter, scopes are unset (or ["users.read","offline.access"]). What scopes are required for this behavior to work reliably (at least users.read)? Future compatibility: Is this internal call or the persisted field set subject to change, and if so, is there guidance or a migration note? Environment better-auth: v1.3.4 Next.js (App Router): v15.1.0 Adapter: drizzle (Postgres) Config (snippet):
socialProviders: {
twitter: {
clientId: process.env.TWITTER_CLIENT_ID!,
clientSecret: process.env.TWITTER_CLIENT_SECRET!,
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/twitter`,
// scope: ["users.read", "offline.access"], // Should we set this explicitly?
},
}
socialProviders: {
twitter: {
clientId: process.env.TWITTER_CLIENT_ID!,
clientSecret: process.env.TWITTER_CLIENT_SECRET!,
redirectURI: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/twitter`,
// scope: ["users.read", "offline.access"], // Should we set this explicitly?
},
}
Notes I’m using databaseHooks.account.create.after and rely on accountData.accountId for post-processing. Email is often unavailable, so I key off providerId + accountId. Thanks in advance!
Solution:
Resolution: the library calls /2/users/me internally. File: packages/better-auth/src/social-providers/twitter.ts In getUserInfo, it calls:...
Jump to solution
1 Reply
Solution
taku
taku2mo ago
Resolution: the library calls /2/users/me internally. File: packages/better-auth/src/social-providers/twitter.ts In getUserInfo, it calls: https://api.x.com/2/users/me?user.fields=profile_image_url https://api.x.com/2/users/me?user.fields=confirmed_email If confirmed_email is present, it sets emailVerified = true. That user.id flows into persistence, so account.accountId is auto-populated with the X user ID. Default scopes: ["users.read","tweet.read","offline.access","users.email"] (unless disableDefaultScope is enabled)

Did you find this page helpful?