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):
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:Jump to solution
Resolution: the library calls /2/users/me internally.
File: packages/better-auth/src/social-providers/twitter.ts
In getUserInfo, it calls:...
1 Reply
Solution
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)