Signup: Modo A vs Modo B (Verificação Postergada)¶
Público-alvo: Desenvolvedores Frontend (Flutter, Web, integrações)
Resumo: A partir de 2026-04-18 (ADR-058), POST /customers/signup aceita a flag verify_email_now, que controla se a verificação de email é exigida antes de emitir JWT (Modo A — comportamento atual) ou postergada para depois do login (Modo B — novo).
⚡ TL;DR para o frontend¶
| Pergunta | Resposta |
|---|---|
| Preciso mudar algo se omitir a flag? | Não. Default verify_email_now=true mantém o fluxo de 2 passos atual. |
| Como ativar Modo B? | Enviar "verify_email_now": false no payload do signup. |
| O que muda na resposta do Modo B? | Em vez de {customer_id, message}, recebe {customer_id, email_verification_pending: true, auth: {access_token, ...}}. |
| Como o usuário verifica depois? | POST /customers/me/verify-email com Bearer token e {code: "123456"}. |
| Conta Modo B pode assinar / pagar / ver vídeos? | Hoje sim (sem gate de verificação ainda — DEBT-045). Recomenda-se que o frontend bloqueie essas ações no UI até o usuário verificar. |
📊 Comparativo dos modos¶
Modo A — Verificação imediata (default, 2 passos)¶
sequenceDiagram
participant FE as Frontend
participant API
participant Email as Resend
FE->>API: POST /customers/signup<br/>{... , verify_email_now: true (ou omitido)}
API-->>FE: 200 {customer_id, message, expires_in_minutes}
Note over FE: Usuario NAO recebe JWT
API->>Email: Envia codigo 6 digitos
Note over FE: Tela de codigo
FE->>API: POST /customers/signup<br/>{..., verification_code: "482917"}
API-->>FE: 200 {docs: [customer], info: {auth}}
Note over FE: JWT recebido
Modo B — Verificação postergada (novo, 1 passo + verificação opcional)¶
sequenceDiagram
participant FE as Frontend
participant API
participant Email as Resend
FE->>API: POST /customers/signup<br/>{..., verify_email_now: false}
API->>Email: Envia codigo 6 digitos
API-->>FE: 200 {customer_id, email_verification_pending: true, auth: {access_token, ...}}
Note over FE: JWT recebido imediatamente
Note over FE: Usuario ja navega na plataforma
Note over FE: Em algum momento...
FE->>API: POST /customers/me/verify-email<br/>Bearer + {code: "482917"}
API-->>FE: 200 {message, email_verified: true, email_verified_at}
🔁 O que mudou no contrato¶
POST /customers/signup¶
Novo campo no payload¶
| Campo | Tipo | Default | Descrição |
|---|---|---|---|
verify_email_now |
bool |
true |
true = Modo A. false = Modo B (JWT imediato, verificação depois). |
Resposta — Modo A (igual ao anterior)¶
Step 1 (sem verification_code):
{
"customer_id": "67abc123def456...",
"email": "joao@example.com",
"message": "Codigo de verificacao enviado para o email.",
"expires_in_minutes": 15
}
Step 2 (com verification_code):
{
"docs": [ { "_id": "...", "username": "...", "email_verified": true, ... } ],
"info": {
"auth": { "access_token": "eyJ...", "token_type": "bearer", "expiration": "..." },
"message": "Email verificado com sucesso.",
"next_step": "/api/v1/customers/me/subscribe"
}
}
Resposta — Modo B (novo)¶
{
"customer_id": "67abc123def456...",
"email": "joao@example.com",
"email_verification_pending": true,
"message": "Conta criada. Verifique seu email mais tarde via POST /customers/me/verify-email.",
"expires_in_minutes": 15,
"auth": {
"access_token": "eyJhbGciOi...",
"archive_token": "eyJ...",
"token_type": "bearer",
"id_token": "67abc123def456...",
"expiration": "2026-04-19T..."
}
}
⚠️ Detecte
email_verification_pending: truepara mostrar banner "Verifique seu email" e habilitar a CTA do endpoint póstumo.
POST /customers/me/verify-email (novo)¶
Autenticado. Permite verificar o email após signup Modo B (ou após qualquer fluxo que tenha deixado o customer com email_verified=false e código ativo).
Request¶
POST /api/v1/customers/me/verify-email
Authorization: Bearer <access_token>
Content-Type: application/json
{ "code": "482917" }
Response — 200 OK¶
{
"message": "Email verificado com sucesso.",
"email_verified": true,
"email_verified_at": "2026-04-18T12:34:56Z"
}
Erros¶
| Status | Causa | Ação no frontend |
|---|---|---|
400 |
Email already verified. |
Esconder UI de verificação, atualizar perfil |
400 |
No active verification code... |
Exibir CTA para reenviar via POST /auth/resend-verification |
400 |
Code expired. ou Codigo incorreto. N tentativa(s) restante(s). |
Mostrar erro inline, permitir nova tentativa |
404 |
Customer not found. |
Sessão inválida, forçar logout |
429 |
Maximum attempts reached. |
Forçar reenvio de novo código |
📱 Implementação Flutter — exemplos prontos¶
Modo A — fluxo atual (sem mudança)¶
Já funciona como hoje. Apenas garanta que seu cliente não envia verify_email_now ou envia true.
Modo B — signup direto + verificação tardia¶
// 1. Signup com verify_email_now=false
Future<Map<String, dynamic>> signupModoB({
required String username,
required String email,
required String password,
required String fullName,
required String phone,
String? taxId,
String? promoCode,
}) async {
final response = await http.post(
Uri.parse('$baseUrl/api/v1/customers/signup'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode([{
'username': username,
'email': email,
'password': password,
'full_name': fullName,
'phone': phone,
'tax_id': taxId,
'promo_code': promoCode,
'verify_email_now': false, // ← chave do Modo B
}]),
);
if (response.statusCode != 200) {
throw Exception('Signup falhou: ${response.body}');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
// data['auth']['access_token'] já está disponível
// data['email_verification_pending'] == true
return data;
}
// 2. Verificação postergada
Future<DateTime> verifyEmailLater({
required String accessToken,
required String code,
}) async {
final response = await http.post(
Uri.parse('$baseUrl/api/v1/customers/me/verify-email'),
headers: {
'Authorization': 'Bearer $accessToken',
'Content-Type': 'application/json',
},
body: jsonEncode({'code': code}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as Map<String, dynamic>;
return DateTime.parse(data['email_verified_at'] as String);
}
if (response.statusCode == 400) {
final detail = jsonDecode(response.body)['detail'] as String;
if (detail.contains('No active verification code')) {
throw NoActiveCodeException(); // dispara reenvio
}
throw VerificationFailedException(detail);
}
if (response.statusCode == 429) {
throw MaxAttemptsException();
}
throw Exception('Erro inesperado: ${response.statusCode}');
}
// 3. Reenviar código (mesma rota pública existente)
Future<void> resendVerificationCode(String email) async {
await http.post(
Uri.parse('$baseUrl/api/v1/auth/resend-verification'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'email': email}),
);
}
🧠 Quando usar cada modo¶
| Cenário | Modo recomendado |
|---|---|
| Cadastro padrão com fricção mínima aceitável | A (default) |
| Onboarding rápido — usuário precisa explorar imediatamente | B |
| Aquisição via campanha paga (preserva conversão) | B |
| Usuário precisa pagar / assinar logo | A (evita comprar com email errado) |
| Conta corporativa / institucional | A (controle de identidade) |
⚠️ Riscos do Modo B (limitações conhecidas)¶
Hoje (estado atual)¶
- Sem gate de recursos (DEBT-045): customer com
email_verified=falseconsegue assinar, pagar e consumir vídeos. Recomenda-se que o frontend trateemail_verifiedlocalmente e bloqueie ações sensíveis no UI. forgot-passwordaceita conta não verificada (DEBT-046): risco de conta perdida se email original for typo/fake.- Sem TTL de conta zumbi (DEBT-047): contas Modo B não verificadas permanecem indefinidamente.
Mitigações no frontend (recomendadas)¶
- Após signup Modo B, persistir
email_verification_pending: truelocalmente. - Exibir banner persistente em telas principais (home, perfil, dashboard) com CTA "Verificar email".
- Antes de chamar
subscribe,purchaseou ações de pagamento, checaremail_verified(doGET /customers/me) e exigir verificação sefalse. - No fluxo "esqueci minha senha", se o usuário sabe que o email pode estar errado, oferecer alternativa (suporte / mudança de email via login).
🔗 Referências¶
- ADR-032 — Verificação de email no signup (regra base, Modo A)
- ADR-058 — Signup dual: verificação imediata vs postergada (esta decisão)
- DEBT-044 — Implementação do Modo B (resolved 2026-04-18)
- DEBT-045 — Gate
get_verified_customer(pendente, alta prioridade) - DEBT-046 —
forgot-passwordsem bloqueio para email não verificado (pendente) - DEBT-047 — TTL de conta zumbi (pendente)
- API Customers — referência completa do endpoint
- Frontend Guide — fluxo geral de cadastro