How to Send OTP with Firebase Functions (2026)
Firebase Functions OTP tutorial using StartMessaging. Callable functions, Firestore for rate-limit, Firebase Auth custom-token issuance after OTP verification.
Firebase is convenient for app backends, but Firebase Phone Auth is not always the right pick for India. This tutorial replaces it with custom OTP via StartMessaging while keeping Firebase Auth for session management.
Why Not Firebase Phone Auth?
See our deep dive on Firebase Auth vs custom OTP .
Setup
firebase init functions
cd functions
npm install firebase-admin firebase-functions
Callable Functions
// functions/src/index.ts
import { initializeApp } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import { onCall, HttpsError } from 'firebase-functions/v2/https';
import { defineSecret } from 'firebase-functions/params';
import { randomUUID } from 'node:crypto';
initializeApp();
const SM_KEY = defineSecret('SM_API_KEY');
export const sendOtp = onCall({ secrets: [SM_KEY] }, async (req) => {
const { phoneNumber } = req.data;
const r = await fetch('https://api.startmessaging.com/otp/send', {
method: 'POST',
headers: { 'X-API-Key': SM_KEY.value(), 'Content-Type': 'application/json' },
body: JSON.stringify({ phoneNumber, idempotencyKey: randomUUID() }),
});
if (!r.ok) throw new HttpsError('unavailable', 'OTP send failed');
return (await r.json()).data;
});
export const verifyOtp = onCall({ secrets: [SM_KEY] }, async (req) => {
const { requestId, otpCode, phoneNumber } = req.data;
const r = await fetch('https://api.startmessaging.com/otp/verify', {
method: 'POST',
headers: { 'X-API-Key': SM_KEY.value(), 'Content-Type': 'application/json' },
body: JSON.stringify({ requestId, otpCode }),
});
if (!r.ok) throw new HttpsError('unauthenticated', 'invalid OTP');
// mint Firebase Auth custom token
const uid = \`phone:\${phoneNumber}\`;
const token = await getAuth().createCustomToken(uid, { phoneNumber });
return { token };
});
Rate Limiting via Firestore
Use a Firestore document per phone with TTL field for hourly limits.
Increment with FieldValue.increment(1).
Firebase Auth Custom Token
After successful verify, mint a custom token. Client signs in with
signInWithCustomToken — and you have a Firebase Auth
identity backed by your own OTP flow.
FAQ
Same idea on Cloud Run? See our Cloud Run guide.
StartMessaging Team
StartMessaging Team