npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@myotp/better-auth

v0.1.1

Published

MyOTP.App phone-number adapter for Better Auth — drop-in SMS, WhatsApp, and Telegram OTP delivery for the better-auth phoneNumber() plugin.

Readme

@myotp/better-auth

npm version npm downloads License: MIT

MyOTP.App phone-number adapter for Better Auth. Drop-in SMS, WhatsApp, and Telegram OTP delivery — a cleaner alternative to Twilio Verify in custom-code mode.

Why

Better Auth's phoneNumber() plugin generates the OTP and stores it in your database, then asks you to deliver it. Most projects end up wiring Twilio (4 credentials, opaque pricing) or rolling their own SMTP-via-Twilio mess. This package gives you a 5-line setup against MyOTP.App: one API key, transparent per-message pricing, three channels (SMS / WhatsApp / Telegram).

This addresses the request in Better Auth issue #4702 for a simpler OTP delivery option in custom mode.

Install

npm install @myotp/better-auth better-auth

Get an API key at myotp.app/sign-up (15 free trial credits, no card).

Use

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, // 5 minutes
    }),
  ],
});

That's it. Better Auth handles generation + verification. MyOTP delivers.

Channel selection

phoneNumber({
  sendOTP: myotpSendOtp({
    apiKey: process.env.MYOTP_API_KEY!,
    channel: "whatsapp", // or "telegram", default "sms"
    brand: "Acme",       // optional sender brand
  }),
})

To make this user-selectable at sign-up time, build a small wrapper:

const sendByChannel = (apiKey: string) => async ({ phoneNumber, code }, request) => {
  const channel = request?.headers?.get("x-otp-channel") ?? "sms";
  const fn = myotpSendOtp({ apiKey, channel });
  return fn({ phoneNumber, code }, request);
};

All options

| Option | Type | Default | Notes | |--------|------|---------|-------| | apiKey | string | (required) | From the MyOTP dashboard or /v1/agent/register | | baseUrl | string | https://api.myotp.app | Override for staging/test | | channel | "sms" \| "whatsapp" \| "telegram" | "sms" | | | brand | string | API key's default | 3-16 alphanumeric, dots allowed | | validitySeconds | number | 300 | 30-14400 (30-3600 for Telegram) | | timeoutMs | number | 15000 | Per-request timeout | | userAgent | string | myotp-better-auth/0.1.0 | | | fetch | typeof fetch | globalThis.fetch | Inject for tests / edge runtimes |

Error handling

The adapter throws MyotpDeliveryError (with .status and .body) on any non-2xx from MyOTP. Better Auth surfaces this back to the caller of signIn.phoneNumber(). Common cases:

  • 403 Insufficient balance — top up at myotp.app
  • 403 IP not whitelisted — add server IP (or * for testing) in dashboard
  • 400 Destination could not be determined — phone format issue (use digits only, no +, no leading 0; library does this for you but exotic numbers may still fail)
import { MyotpDeliveryError } from "@myotp/better-auth";

try {
  await auth.api.sendVerificationCode({ phoneNumber: "..." });
} catch (err) {
  if (err instanceof MyotpDeliveryError && err.status === 403) {
    // ask user to retry / contact support
  }
}

What this does (and doesn't)

Does: delivers an OTP code that Better Auth generated, by calling MyOTP's /generate_otp with the otp_code parameter set to Better Auth's value. MyOTP just acts as the delivery channel.

Does NOT: verify the code against MyOTP. Better Auth verifies against its own database. MyOTP's /verify_otp endpoint is not invoked.

This split matches the design pattern Supabase uses for its phone-auth hooks. It's the right boundary — keep state in your auth library, treat the SMS provider as a delivery service.

Edge runtimes

Works on Vercel Edge, Cloudflare Workers, Bun, Deno — anywhere globalThis.fetch exists. If your runtime needs a custom fetch (Node < 18, jsdom-based tests), pass it explicitly:

import { fetch as undiciFetch } from "undici";
myotpSendOtp({ apiKey: "...", fetch: undiciFetch });

Testing your wiring

# 1. Get a key
npx myotp init             # interactive (after @myotp/cli ships)
# or sign up manually at https://myotp.app/sign-up

# 2. Verify the adapter directly
node --input-type=module -e '
  import("@myotp/better-auth").then(({ myotpSendOtp }) =>
    myotpSendOtp({ apiKey: process.env.MYOTP_API_KEY })({
      phoneNumber: "14155551234",
      code: "123456",
    }).then(() => console.log("delivered"))
  );
'

Submitting upstream

We're working with the Better Auth team to list this in their official docs. Track issue #4702.

License

MIT — BroadNet Technologies. See LICENSE.