quickstart
First OTP in 5 minutes
From zero to a verified session with a signed JWT. No payment, no KYC — the test environment is fully simulated.
1. Create an app
Dashboard → New app → you get two environments: test (free, simulated OTP) and live (real sends, credits). Keys:
your keys
pk_test_xxx — publishable, safe in browsers/mobile apps
sk_test_xxx — secret, server-side only
whsec_xxx — webhook signing secret
(pk_live_/sk_live_ on the live environment)2. Send an OTP
curl
curl https://api.nexauth.dev/v1/otp/send \
-H "Authorization: Bearer pk_test_xxx" \
-d '{"channel":"whatsapp","identifier":"+919999900001"}'
→ {"otp_id":"…","status":"sent","dev_code":"4570437"}
test env returns dev_code — no message is sent3. Verify → session
curl
curl https://api.nexauth.dev/v1/otp/verify \
-H "Authorization: Bearer pk_test_xxx" \
-d '{"channel":"whatsapp","identifier":"+919999900001","code":"4570437"}'
→ {"access_token":"eyJ…","refresh_token":"…","expires_in":900,"new_user":true}4. Verify the JWT anywhere — offline
Tokens are RS256, signed per-tenant. Your backend verifies offline against our JWKS — no network call, no secret sharing:
go
jwks := nexauth.NewJWKS("https://api.nexauth.dev/v1/jwks.json")
claims, err := jwks.Verify(accessToken)
// claims: sub, app_id, env, roles[]5. Plug the SDK instead
typescript
import NexAuth from "@nexauth/sdk";
const auth = new NexAuth({ publishableKey: "pk_test_xxx" });
await auth.otp.send({ channel: "whatsapp", to: phone });
const session = await auth.otp.verify({ channel: "whatsapp", to: phone, code });What just happened?
- The OTP was hashed (never stored plaintext), rate-limited (10/min/IP + 10/hour/identifier), and would debit credits in live env — after successful send only.
- The user was created inside your app's RLS-isolated row space — invisible to every other tenant.
- The JWT is signed by a keypair unique to your app+env, lazily generated, private key AES-256-GCM'd at rest.
- Webhooks fired:
user.created,session.started,otp.sent.
Next
- Next.js SDK — middleware, route handlers, components
- Flutter SDK — the integration OTPless couldn't give you
- Migrate existing users — bcrypt/Firebase passwords just work