Para SaaS construídos no Lovable

Assinatura digital no seu SaaS Lovable — em 3 prompts

Cole 3 prompts no chat do Lovable e ganhe assinatura com validade jurídica brasileira — ICP-Brasil A1/A3, CPF/CNPJ, biometria e OTP. Sem abrir editor de código.

Demo ao vivo em sign-docs-brasil.lovable.app · 3 prompts · Pronto em 15 minutos · MP 2.200-2 · LGPD

Stack suportada

React + TypeScript Vite Supabase shadcn/ui Tailwind
arquitetura.tsx

React UI (Lovable)

Botão "Assinar"

Edge Function (Supabase)

create-signing-session

SignDocs Brasil API

/v1/signing-sessions

↩ webhook

Supabase Realtime

Atualiza a UI na hora

Lovable + SignDocs Brasil

Validade Jurídica

MP 2.200-2/2001

ICP-Brasil A1/A3

Certificado digital nativo

Webhook HMAC-SHA256

LGPD · dados em sa-east-1

SDK TypeScript oficial

@signdocs-brasil/api no npm

Para quem é esta integração

Você descobriu o Lovable, fez um SaaS em um fim de semana e agora um cliente pediu assinatura de contrato. Aqui está o caminho mais curto.

Fundadores solo + vibe coders

Validou um MVP no Lovable e o primeiro contrato entrou. Ganhe validade jurídica brasileira em horas, não semanas, sem trocar de stack.

Agências e consultorias

Entregue SaaS sob encomenda com e-sig ICP-Brasil já embutido. Cobre pela entrega, não pela integração — todo o boilerplate está neste guia.

Operações e RH internas

Construiu um portal interno no Lovable para contratos de fornecedor, admissão ou aditivo? Tenha assinatura com evidência forense no mesmo app.

Como a integração funciona

Quatro passos. O frontend fica no Lovable, o segredo fica no Supabase.

1

Gere credenciais HML

Self-service em app.signdocs.com.br via wizard de plano. Conta PJ → Perfil → Gerenciar Plano → escolha Enterprise → Receber Orçamento → API Dashboard → Ativar credenciais HML. ~3 minutos. Fluxo detalhado de 9 passos.

2

Habilite Lovable Cloud

Quando o Lovable oferecer "Enable Cloud" mid-fluxo, aceite — ele faz auto-deploy de migrations e Edge Functions. Adicione 4 secrets no painel Supabase do Cloud. O client_secret nunca toca o browser.

3

3 prompts no Lovable

Cole os prompts do guia no chat. O Lovable gera a tabela, as Edge Functions e os componentes React para você — código testado em HML.

4

Preview + Realtime

Teste no preview do Lovable com um PDF real. Webhook atualiza o badge de status em tempo real via Supabase Realtime — sem polling.

Os 3 prompts que fazem o trabalho pesado

Antes: adicione os 4 secrets no painel Supabase (Edge Functions → Secrets). Depois: cole estes prompts no chat do Lovable, em ordem. O Lovable gera a tabela, as funções serverless e os componentes React — o código fica colapsado abaixo, para você conferir se a geração saiu igual à referência testada em HML.

Prefere colar um prompt só? Use o mega-prompt do starter — scaffolda os três em uma única geração.

Antes dos prompts: 4 secrets no Supabase

Supabase → Edge Functions → Secrets

No painel Supabase, adicione estas 4 chaves. Sem elas, nenhuma Edge Function vai conseguir chamar o SignDocs.

SIGNDOCS_CLIENT_ID        = <enviado pela equipe SignDocs>
SIGNDOCS_CLIENT_SECRET    = <enviado pela equipe SignDocs>
SIGNDOCS_BASE_URL         = https://api-hml.signdocs.com.br
SIGNDOCS_WEBHOOK_SECRET   = <deixe vazio por enquanto>
⚠️ Erro nº1: HML usa api-hml (hífen), nunca api.hml (ponto). Copiar errado dá timeout ou 404 silencioso.
ℹ️ HML é efêmero: todos os documentos, sessões e evidências em homologação são apagados em até 7 dias, por design. Use apenas PDFs descartáveis, CPFs fictícios e emails de teste — nunca envie dados reais para HML. Para produção, troque SIGNDOCS_BASE_URL e as credenciais (sem mudança de código).
1

Prompt: tabela envelope_status

colar no chat do Lovable

Cria o espelho local do status da assinatura. O webhook escreve aqui, o React lê em tempo real.

Crie uma tabela Postgres chamada "envelope_status" no Supabase com estas colunas:

  - session_id     text PRIMARY KEY
  - transaction_id text
  - user_id        uuid REFERENCES auth.users(id) ON DELETE CASCADE
  - status         text NOT NULL DEFAULT 'PENDING'
  - evidence_id    text
  - updated_at     timestamptz NOT NULL DEFAULT now()

Habilite Row Level Security. Adicione uma policy SELECT chamada
"owners read their sessions" com USING (auth.uid() = user_id).

Inclua a tabela na publicação supabase_realtime para que o frontend
receba updates em tempo real quando o webhook mudar o status.
Ver SQL de referência (para comparar com o que o Lovable gerou)
create table public.envelope_status (
  session_id     text primary key,
  transaction_id text,
  user_id        uuid references auth.users(id) on delete cascade,
  status         text not null default 'PENDING',
  evidence_id    text,
  updated_at     timestamptz not null default now()
);

alter table public.envelope_status enable row level security;

create policy "owners read their sessions"
  on public.envelope_status for select
  using (auth.uid() = user_id);
2

Prompt: Edge Function create-signing-session

colar no chat do Lovable

Núcleo da integração: autentica o usuário, chama o SignDocs, monta o signingUrl e grava o status inicial.

Crie uma Supabase Edge Function chamada "create-signing-session".

ENTRADA (JSON): signerName, signerEmail, signerCpf (OBRIGATÓRIO,
11 dígitos), pdfBase64, filename (opcional), returnUrl (LIMPO, sem
placeholders — SignDocs adiciona ?session_id= no redirect).

SECRETS de Deno.env: SIGNDOCS_CLIENT_ID, SIGNDOCS_CLIENT_SECRET,
SIGNDOCS_BASE_URL, SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY.

PASSO 1 — Validar Bearer JWT do Supabase Auth via supabase.auth.getUser().
         Se inválido, retornar 401.

PASSO 2 — Chamar SDK "@signdocs-brasil/api@1.3.0":
  signingSessions.create({
    purpose: "DOCUMENT_SIGNATURE",
    policy:  { profile: "CLICK_ONLY" },
    signer:  { name, email, cpf, userExternalId: userData.user.id },
    document:{ content: pdfBase64, filename: filename ?? "contrato.pdf" },
    returnUrl, locale: "pt-BR",
  })

PASSO 3 — Montar signingUrl = session.url + "?cs=" +
         encodeURIComponent(session.clientSecret). OBRIGATÓRIO —
         sem o ?cs a página retorna 401.

PASSO 4 — Upsert em envelope_status usando session.sessionId (NÃO
         session.id — esse campo é undefined no SDK):
         { session_id: session.sessionId, transaction_id: session.transactionId,
           user_id, status: "PENDING" }.

SAÍDA: { sessionId: session.sessionId, signingUrl, expiresAt }.
Apenas POST. Outros: 405.
⚠️ 5 coisas que o Lovable erra em ~30% das gerações (descobertas em teste real): (1) esquece de concatenar ?cs=, (2) usa session.id em vez de session.sessionId (primeiro é undefined), (3) faz deploy com --no-verify-jwt (só o webhook usa essa flag), (4) adiciona placeholder __SESSION_ID__ no returnUrl que nunca é substituído, (5) vaza SERVICE_ROLE_KEY para arquivos fora de supabase/functions/.
Ver TypeScript de referência (para comparar com o que o Lovable gerou)
import { SignDocsBrasilClient } from "npm:@signdocs-brasil/api@1.3.0";
import { createClient } from "npm:@supabase/supabase-js@2";

const signdocs = new SignDocsBrasilClient({
  clientId:     Deno.env.get("SIGNDOCS_CLIENT_ID")!,
  clientSecret: Deno.env.get("SIGNDOCS_CLIENT_SECRET")!,
  baseUrl:      Deno.env.get("SIGNDOCS_BASE_URL")!,
});

const supabase = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

Deno.serve(async (req) => {
  if (req.method !== "POST") return new Response("Method Not Allowed", { status: 405 });

  const authHeader = req.headers.get("Authorization") ?? "";
  const { data: userData, error: userErr } = await supabase.auth.getUser(
    authHeader.replace("Bearer ", ""),
  );
  if (userErr || !userData.user) return new Response("Unauthorized", { status: 401 });

  const { signerName, signerEmail, signerCpf, pdfBase64, filename, returnUrl } = await req.json();

  // CPF obrigatório + rejeita placeholders no returnUrl
  const cpfDigits = (signerCpf ?? "").replace(/\D/g, "");
  if (cpfDigits.length !== 11) return Response.json({ error: "CPF obrigatório (11 dígitos)" }, { status: 400 });
  if (returnUrl?.includes("__SESSION_ID__")) return Response.json({ error: "returnUrl não deve conter placeholders" }, { status: 400 });

  const session = await signdocs.signingSessions.create({
    purpose: "DOCUMENT_SIGNATURE",
    policy:  { profile: "CLICK_ONLY" },
    signer:  { name: signerName, email: signerEmail, cpf: cpfDigits,
               userExternalId: userData.user.id },
    document:{ content: pdfBase64, filename: filename ?? "contrato.pdf" },
    returnUrl, locale: "pt-BR",
  });

  const signingUrl = `${session.url}?cs=${encodeURIComponent(session.clientSecret)}`;

  await supabase.from("envelope_status").upsert({
    session_id:     session.sessionId,
    transaction_id: session.transactionId,
    user_id:        userData.user.id,
    status:         "PENDING",
  });

  return Response.json({
    sessionId: session.sessionId,
    signingUrl,
    expiresAt: session.expiresAt,
  });
});
3

Prompt: componentes React (hook + botão + badge)

colar no chat do Lovable

Hook que dispara a assinatura, botão pronto para o shadcn/ui e um badge de status que atualiza em tempo real via Supabase Realtime — sem polling.

Crie 3 arquivos React/TypeScript para o fluxo de assinatura por redirect:

ARQUIVO 1 — src/hooks/useSignDocs.ts
  - Exporta useSignDocs() com async startSigning(input).
  - input: { signerName, signerEmail, signerCpf (obrigatório), pdfBase64, filename? }.
  - Chama supabase.functions.invoke("create-signing-session", {
      body: { ...input, returnUrl: `${window.location.origin}/assinado` }
    }) e faz window.location.href = data.signingUrl.

ARQUIVO 2 — src/components/SignButton.tsx
  - Props: pdfBase64, signer ({ name, email, cpf? }).
  - Button do shadcn/ui com "Enviar para assinatura", chama startSigning.

ARQUIVO 3 — src/components/SigningStatus.tsx
  - Prop: sessionId.
  - No mount, SELECT inicial em envelope_status.
  - Inscreve em canal Supabase Realtime com
    filter: `session_id=eq.${sessionId}` escutando UPDATE.
  - Badge (shadcn/ui): variant="default" se COMPLETED, "secondary" senão.
  - Remove canal no cleanup do useEffect.

Crie também a rota /assinado com uma tela de sucesso simples.
Ver TypeScript de referência (os 3 arquivos)
// src/hooks/useSignDocs.ts
import { supabase } from "@/integrations/supabase/client";

export function useSignDocs() {
  async function startSigning(input: {
    signerName: string; signerEmail: string; signerCpf: string;  // obrigatório
    pdfBase64: string; filename?: string;
  }) {
    const { data, error } = await supabase.functions.invoke(
      "create-signing-session",
      { body: { ...input, returnUrl: `${window.location.origin}/assinado` } },
    );
    if (error) throw error;
    window.location.href = data.signingUrl;
  }
  return { startSigning };
}

// src/components/SigningStatus.tsx  (realtime, sem polling)
import { useEffect, useState } from "react";
import { supabase } from "@/integrations/supabase/client";
import { Badge } from "@/components/ui/badge";

export function SigningStatus({ sessionId }: { sessionId: string }) {
  const [status, setStatus] = useState("PENDING");

  useEffect(() => {
    supabase.from("envelope_status").select("status")
      .eq("session_id", sessionId).single()
      .then(({ data }) => data && setStatus(data.status));

    const channel = supabase.channel(`envelope:${sessionId}`)
      .on("postgres_changes",
        { event: "UPDATE", schema: "public", table: "envelope_status",
          filter: `session_id=eq.${sessionId}` },
        (payload) => setStatus((payload.new as { status: string }).status),
      ).subscribe();

    return () => { supabase.removeChannel(channel); };
  }, [sessionId]);

  return (
    <Badge variant={status === "COMPLETED" ? "default" : "secondary"}>
      {status}
    </Badge>
  );
}

Há ainda um 4º prompt para o webhook (com verificação HMAC) e um 5º opcional para o fluxo embed em iframe. Ambos estão no guia completo.

Casos de uso para SaaS brasileiro

Onde SaaS construídos no Lovable já colocam SignDocs Brasil em produção.

Imobiliárias e plataformas de locação

Cadastro do inquilino na app Lovable → geração do contrato de locação → assinatura do locador e locatário via envelope paralelo → arquivamento no Drive.

App Lovable Edge Fn Envelope

Freelancer / Agência SaaS

Cliente aceita proposta no app do freelancer (Lovable) → contrato de prestação de serviço é gerado e assinado por OTP → link de pagamento disparado.

Proposta OTP + PDF Checkout

Health-tech / Clínicas

Pacientes preenchem termo de consentimento ou contrato de plano no portal Lovable da clínica. Política BIOMETRIC garante prova irrefutável da identidade do signatário.

Paciente Biometria Evidence .p7m

Ed-tech e cursos online

Matrícula no curso dispara contrato de prestação educacional + autorização LGPD para imagem. Alunos assinam direto na plataforma sem sair do app.

Matrícula Envelope 2x Aula liberada

Redirect ou Embed? Dois caminhos, uma escolha

O guia técnico cobre os dois. Comece pelo redirect — é mais robusto. Faça upgrade para embed só quando a UX no produto exigir.

Critério Redirect (recomendado) Embed (iframe)
Tempo para ir ao ar ~15 minutos ~45 minutos + whitelist de domínio
UX sem sair do app redireciona
Mobile (iOS Safari, PWA) 100% estável Biometria em iframe tem bugs — fallback para redirect
Precisa liberar CSP / frame-ancestors não Sim — peça whitelist do seu domínio
Fonte da verdade = webhook
Ideal para MVP, mobile, público-geral Portal B2B desktop com branding forte

Você pode começar com redirect e migrar para embed depois — a única mudança é no componente React. Edge Functions e webhook permanecem iguais.

Perguntas frequentes

Respostas práticas para quem está saindo do protótipo para a produção.

Posso chamar a API do SignDocs direto do frontend Lovable?

Não. O SignDocs usa OAuth2 client_credentials, e o client_secret nunca pode ir para o browser. Toda a integração passa por uma Supabase Edge Function que atua como proxy server-side — esse é o padrão nativo do Lovable para APIs autenticadas.

O Lovable gera o código da Edge Function para mim?

Sim — essa é exatamente a proposta do guia. Fornecemos prompts prontos para colar no chat do Lovable: um para a tabela Postgres, outro para cada Edge Function, outro para os componentes React. O Lovable gera o código a partir do prompt.

Você confere o resultado contra a referência colapsada que acompanha cada prompt no guia — garantindo que os 3 pontos que o Lovable costuma errar (?cs= no signingUrl, deploy sem --no-verify-jwt na função de criação, HMAC com timingSafeEqual no webhook) saíram corretos. Se algo divergir, o guia traz prompts de correção prontos para colar.

Preciso usar Supabase, ou posso usar outro backend?

O Lovable tem o Supabase como backend padrão, mas o padrão de integração é o mesmo para qualquer backend: um endpoint server-side que guarda o client_secret e chama a API do SignDocs. Se você exportou o projeto para Vercel/Railway/Cloudflare Workers, só troque as Edge Functions por Serverless Functions equivalentes.

Qual a validade jurídica das assinaturas geradas?

Total. Seguem a MP 2.200-2/2001 e geram um pacote de evidências (.p7m) com hash do documento, geolocalização, IP, timestamp, método de autenticação e, quando aplicável, certificado ICP-Brasil A1 ou A3. É o mesmo padrão usado no app web oficial do SignDocs.

Dá para embutir a tela de assinatura dentro do meu SaaS (iframe)?

Sim. Nosso guia cobre os dois caminhos. O redirect é o padrão recomendado (mais simples, funciona em mobile). O embed em iframe requer liberação do seu domínio na lista de frame-ancestors pelo time SignDocs, e cai de volta para redirect em mobile Safari para evitar bugs de permissão de câmera em iframes aninhados.

Como recebo a confirmação quando o usuário terminar de assinar?

Via webhook. Você registra uma Edge Function como endpoint público, o SignDocs envia POST a cada evento (TRANSACTION.COMPLETED, TRANSACTION.CANCELLED, etc.), e a função verifica o HMAC-SHA256 antes de atualizar o Supabase. O componente React recebe a mudança em tempo real via Supabase Realtime, com polling leve de 3s como fallback para garantir convergência.

Bônus: o SignDocs já envia automaticamente um email de confirmação ao signatário a partir de no-reply@mg.signdocs.com.br com o ID da evidência. Não precisa construir esse fluxo no seu app — só se quiser personalizar.

Lovable Cloud ou external Supabase via Connectors?

Lovable Cloud (recomendado). Quando o Lovable oferece "Enable Cloud" durante o setup, aceite. Cloud aplica migrations e deploya Edge Functions automaticamente — o que viabiliza a UX de "remix → funciona". Avisamos: Cloud é one-way para o projeto (não pode ser desabilitado depois), mas isso é OK porque não tem motivo para voltar atrás depois.

External Supabase via Connectors é alternativa avançada: você ganha controle do projeto (região São Paulo, dashboard direto), mas perde o auto-deploy. Lovable gera o código e você precisa aplicar migrations e fazer deploy de Edge Functions manualmente via SQL Editor + Supabase CLI. Para um time desenvolvedor sênior que quer controle total, é viável; para integradores rápidos, Cloud é melhor.

O signingUrl retornado pela API já está pronto para uso?

Quase. A API retorna session.url e session.clientSecret em campos separados — você precisa combinar como url + "?cs=" + encodeURIComponent(clientSecret). Nosso guia mostra o código exato. Passar a url sem o cs resulta em erro 401 na tela hospedada.

Tem limite de volume no HML? Quando migro para produção?

HML tem cota reduzida e serve para testes. Quando o fluxo estiver estável, troque SIGNDOCS_BASE_URL de api-hml.signdocs.com.br para api.signdocs.com.br, registre o webhook no tenant prod (recebe um novo secret) e faça um smoke-test antes de liberar para todos os usuários. Nenhuma mudança de código.

Os documentos e assinaturas em HML são preservados?

Não. O ambiente HML é efêmero: todos os documentos, sessões de assinatura, evidências (.p7m) e logs são apagados automaticamente em até 7 dias. Use HML apenas para integração e testes com PDFs descartáveis, CPFs fictícios e emails de teste. Nunca envie dados reais, contratos válidos ou PII de clientes para HML — vão ser apagados e não há recuperação. Para produção, troque SIGNDOCS_BASE_URL e credenciais, sem mudança de código.

Webhook registrado mas não chega no meu app local — o que houve?

O WAF do SignDocs bloqueia webhooks apontando para localhost ou IPs privados (retorna CloudFront 403). Projetos Supabase hospedados têm URL pública e funcionam direto. Para dev local, use um tunnel tipo ngrok ou cloudflared.

Quanto custa? Tem plano grátis para SaaS em MVP?

Planos a partir de R$ 14,90/mês incluem cota de documentos para SaaS em fase inicial. HML é gratuito para integração e testes ilimitados. Para volume alto ou SLA dedicado, existe o plano Enterprise — fale com o time comercial.

Tem dúvidas sobre a integração?

Credenciais HML agora são self-service — crie uma conta grátis em app.signdocs.com.br e gere client_id e client_secret direto no dashboard. Esta caixa é para dúvidas técnicas — nossa equipe responde em até 1 dia útil.

Respondemos em até 1 dia útil. Sem spam.

Leve seu SaaS Lovable para produção com validade jurídica

O guia técnico oficial tem o passo-a-passo completo com código testado em HML. Comece agora, ative prod quando o fluxo estiver no azul.

Também disponível em API REST pura · nó n8n · Telegram Bot