Skip to content

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: true para 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=false consegue assinar, pagar e consumir vídeos. Recomenda-se que o frontend trate email_verified localmente e bloqueie ações sensíveis no UI.
  • forgot-password aceita 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)

  1. Após signup Modo B, persistir email_verification_pending: true localmente.
  2. Exibir banner persistente em telas principais (home, perfil, dashboard) com CTA "Verificar email".
  3. Antes de chamar subscribe, purchase ou ações de pagamento, checar email_verified (do GET /customers/me) e exigir verificação se false.
  4. 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-046forgot-password sem 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