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 UI (Lovable)
Botão "Assinar"
Edge Function (Supabase)
create-signing-session
SignDocs Brasil API
/v1/signing-sessions
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
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.
Validou um MVP no Lovable e o primeiro contrato entrou. Ganhe validade jurídica brasileira em horas, não semanas, sem trocar de stack.
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.
Construiu um portal interno no Lovable para contratos de fornecedor, admissão ou aditivo? Tenha assinatura com evidência forense no mesmo app.
Quatro passos. O frontend fica no Lovable, o segredo fica no Supabase.
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.
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.
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.
Teste no preview do Lovable com um PDF real. Webhook atualiza o badge de status em tempo real via Supabase Realtime — sem polling.
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.
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>
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.
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);
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.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,
});
});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.// 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.
Onde SaaS construídos no Lovable já colocam SignDocs Brasil em produçã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.
Envelope
Cliente aceita proposta no app do freelancer (Lovable) → contrato de prestação de serviço é gerado e assinado por OTP → link de pagamento disparado.
OTP + PDF
→
Checkout
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.
Biometria
→
Evidence .p7m
Matrícula no curso dispara contrato de prestação educacional + autorização LGPD para imagem. Alunos assinam direto na plataforma sem sair do app.
Envelope 2x
→
Aula liberada
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.
Respostas práticas para quem está saindo do protótipo para a produção.
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.
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.
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.
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.
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.
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 (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.
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.
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.
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.
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.
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.
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.
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