Better Auth's phoneNumber() plugin does the hard part of phone login. It generates the code, stores it, verifies it and manages the session. What it leaves to you is delivery: a sendOTP callback that you fill in. I work at MyOTP.App and we publish an adapter for that callback, so here's the whole setup.
Install
npm install @myotp/better-auth better-auth
Get a key at myotp.app/sign-up. 15 trial credits, no card.
Wire it
import { betterAuth } from "better-auth";
import { phoneNumber } from "better-auth/plugins";
import { myotpSendOtp } from "@myotp/better-auth";
export const auth = betterAuth({
database: yourDatabase(),
plugins: [
phoneNumber({
sendOTP: myotpSendOtp({ apiKey: process.env.MYOTP_API_KEY! }),
otpLength: 6,
expiresIn: 300,
}),
],
});
That's it. Better Auth generates and verifies, MyOTP delivers.
Channel
myotpSendOtp({
apiKey: process.env.MYOTP_API_KEY!,
channel: "whatsapp", // or "telegram", default "sms"
brand: "Acme", // optional sender name
})
To let the user choose at signup, wrap it: read the channel from your own request context and call myotpSendOtp with that value. The README has the wrapper.
Two things to know
Better Auth owns the code, so MyOTP never sees the verification. That means our delivery report tells you the message arrived, and Better Auth tells you whether the user typed it right. Two systems, two logs, and you'll want both when debugging a "code never came" ticket.
The key lives in your server environment. The adapter runs inside Better Auth on the server, so it never reaches the client bundle. Allowlist the server's public IP on the key in the MyOTP dashboard, or the first send from production comes back 403. That 403 means the allowlist, not a bad key.
Why not just call Twilio in the callback
You can. The callback is a function, it doesn't care. The reasons people pick us for it are setup, price outside the US, and WhatsApp and Telegram as channels without going through Meta's business verification. If your users are US-only and you already have a Twilio account, stay put.
Disclosure again: I work at MyOTP.App. The adapter is MIT, on npm and GitHub at brntech/myotp-agentkit.