Pular para o conteúdo
📖 COOKBOOK

WhatsApp OTP em 8 linhas.

Único do SuperDB: OTP entregue via WhatsApp em vez de SMS. Pra apps brasileiros, é gigantesco — usuário não fecha conta no WhatsApp. Mostra send, verify, rate limit e fallback pra SMS se número não tem WhatsApp.

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):

app/login/whatsapp-form.tsx
'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:

Template WhatsApp (Meta-approved)
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:

terminal — debug com cURL
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:

app/login/whatsapp-form.tsx
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.

WhatsApp · +55 11 4002-8922
v0.4.2
Seu código de acesso ao <strong>Meu App</strong> é:
478 291
Não compartilhe. Válido por 10 minutos.
14:32 ✓✓

Variações

Signup completo com data adicional

Capture nome, plano, referrer no momento do OTP. Tudo entra em user_metadata:

signup-whatsapp.ts
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:

app/perfil/adicionar-whatsapp.tsx
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).

Essa página ajudou?