O que vamos fazer
- Input de telefone com mask brasileira (
+55 (DD) 9XXXX-XXXX) - Botão "Enviar código" que dispara OTP via WhatsApp em ~3 segundos
- Input de 6 dígitos pro usuário digitar o código recebido
- Verify → sessão criada e usuário logado
- Fallback automático pra SMS quando o número não tem WhatsApp
Pré-requisitos
- Plano Pro+ (Free não tem WhatsApp por causa do custo da Meta Cloud API)
- Provider WhatsApp configurado no Dashboard (Z-API, Twilio WhatsApp Business, ou Meta Cloud API direto)
- Número de teste verificado com a Meta (ou número de produção aprovado)
- Template de mensagem aprovado pela Meta (o SuperDB já fornece um padrão)
Por que importa pra Brasil: 99% dos brasileiros adultos têm WhatsApp e checam em minutos. SMS no Brasil custa 4× mais que WhatsApp e tem taxa de entrega menor (operadoras filtram). Pra apps B2C, WhatsApp OTP reduz drop-off de signup em 20-30%.
Passo 1 — Frontend com input formatado
Use uma lib de mask ou regex inline. Importante: enviar pro backend já em formato E.164 (+5511999998888):
'use client'
import { useState } from 'react'
import { createBrowserClient } from '@superdb/supabase-compat'
function formatBR(raw: string) {
const digits = raw.replace(/\D/g, '').slice(0, 11)
if (digits.length <= 2) return digits
if (digits.length <= 7) return `(${digits.slice(0, 2)}) ${digits.slice(2)}`
return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7)}`
}
function toE164(formatted: string) {
return '+55' + formatted.replace(/\D/g, '')
}
export function WhatsAppForm() {
const [phone, setPhone] = useState('')
const [enviado, setEnviado] = useState(false)
const [code, setCode] = useState('')
const db = createBrowserClient(
process.env.NEXT_PUBLIC_SUPERDB_URL!,
process.env.NEXT_PUBLIC_SUPERDB_ANON_KEY!
)
async function enviar() {
const { error } = await db.auth.signInWithOtp({
phone: toE164(phone),
options: { channel: 'whatsapp' },
})
if (!error) setEnviado(true)
}
async function verificar() {
const { error } = await db.auth.verifyOtp({
phone: toE164(phone),
token: code,
type: 'sms', // tipo unificado pra phone OTP
})
if (!error) location.href = '/app'
}
if (!enviado) return (
<>
<input value={phone} onChange={(e) => setPhone(formatBR(e.target.value))}
placeholder="(11) 99999-8888" />
<button onClick={enviar}>Enviar código via WhatsApp</button>
</>
)
return (
<>
<p>Código enviado pro WhatsApp {phone}</p>
<input value={code} onChange={(e) => setCode(e.target.value)}
maxLength={6} placeholder="000000" />
<button onClick={verificar}>Verificar</button>
</>
)
}
Passo 2 — Como o envio funciona
O SuperDB recebe a chamada, verifica rate limit, e envia via provider configurado. Template típico aprovado pela Meta:
Seu código de acesso ao Meu App é: 478291
Não compartilhe este código com ninguém.
Válido por 10 minutos.
Em produção, customize no Dashboard em Auth → SMS Templates → WhatsApp. Limite Meta: 1024 caracteres, sem links externos (cai como spam).
Passo 3 — Verify e criação da sessão
Quando o user digita o código, o verifyOtp valida server-side e cria a sessão:
curl -X POST "$SUPERDB_URL/auth/v1/verify" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+5511999998888",
"token": "478291",
"type": "sms"
}'
# Retorna:
# { "access_token": "eyJ...", "refresh_token": "...", "user": {...} }
Note: type: 'sms' mesmo pra WhatsApp — o channel é só pra envio; pra verify, o code é o mesmo formato.
Passo 4 — Fallback pra SMS
Nem todo número tem WhatsApp ativo. Quando o send falha com whatsapp_unavailable, re-tente com SMS:
async function enviarComFallback() {
const e164 = toE164(phone)
// 1ª tentativa: WhatsApp
const r1 = await db.auth.signInWithOtp({
phone: e164,
options: { channel: 'whatsapp' },
})
if (r1.error?.code === 'whatsapp_unavailable') {
// Fallback: SMS
const r2 = await db.auth.signInWithOtp({
phone: e164,
options: { channel: 'sms' },
})
if (r2.error) return alert('Não foi possível enviar o código')
setCanal('sms') // mostra "Código enviado por SMS" em vez de WhatsApp
}
setEnviado(true)
}
Resultado
App brasileiro com login de 30 segundos via WhatsApp. Em testes A/B em apps B2C brasileiros (delivery, fintech, marketplace), WhatsApp OTP supera SMS em conversão de signup.
Variações
Signup completo com data adicional
Capture nome, plano, referrer no momento do OTP. Tudo entra em user_metadata:
await db.auth.signInWithOtp({
phone: '+5511999998888',
options: {
channel: 'whatsapp',
shouldCreateUser: true,
data: {
nome: 'Felipe Padilha',
plano: 'pro_trial',
origem: 'landing_b',
},
},
})
Vincular WhatsApp a conta existente
User logado com email pode adicionar telefone WhatsApp como segundo método de login. Útil pra "lembrar de mim" persistente:
const { error } = await db.auth.updateUser({
phone: '+5511999998888',
})
// SuperDB envia OTP via WhatsApp pra confirmar posse
// User digita código → phone fica em auth.users.phone_confirmed_at
Configurar rate limit
Padrão: 5 OTPs/hora por número. Mude no Dashboard → Auth → Rate Limits → OTP per phone. Pra apps de alta sensibilidade (banking), baixe pra 3/hora. Pra apps casuais, suba pra 10/hora.
Erros comuns
Número sem código do país
O parâmetro phone precisa estar em E.164: +5511999998888. Sem o +, sem o 55, ou com hífens/parênteses → 400 invalid_phone. Sempre normalize antes de enviar (use a função toE164 mostrada no Passo 1).
Provider WhatsApp não verificado
Erro: "O número não pode receber mensagens via WhatsApp Business". Causa: o Business Account no Meta ainda não está aprovado pra produção (modo Sandbox só aceita números pré-cadastrados). Solução: complete a verificação Meta Business e suba do tier tier_unverified pra tier_50.
Atenção em produção: a Meta cobra por mensagem enviada (~R$0,07 por OTP no Brasil). Em ataques de SMS pumping, isso vira prejuízo rápido. Configure captcha (Turnstile) antes do botão de enviar OTP, e mantenha rate limit por IP além do por número.
Usuário bloqueou seu número
Se o user já bloqueou o número Business da sua empresa, o WhatsApp aceita o envio mas a mensagem nunca chega. Você não consegue detectar isso. Mitigação: depois de 2min sem confirm, ofereça botão "não recebi, enviar por SMS" — não fique repetindo o envio via WhatsApp.
Sobre conformidade LGPD: o número de telefone é dado pessoal. Mostre o aviso de privacidade ANTES do botão de envio, não depois. O SuperDB já trata o número como PII e cifra em repouso (Sprint 5).