Transforme "proposta enviada" em "contrato fechado" no mesmo dia: o cliente recebe a proposta, aceita online com um clique e o seu CRM marca o deal como ganho automaticamente. O aceite eletrônico tem validade jurídica fundada na MP 2.200-2/2001 (art. 10, §2º — validade pelo consentimento das partes), com trilha de auditoria completa, hash SHA-256 do documento e pacote de evidências assinado com certificado ICP-Brasil, em conformidade com a LGPD. Menos fricção no fechamento significa ciclo de vendas mais curto e menos propostas perdidas por follow-up manual.
Público-alvo: times de vendas e RevOps que querem fechar contrato mais rápido, e desenvolvedores que integram CRM (HubSpot, Pipedrive, RD Station, Salesforce ou CRM próprio) a um fluxo de aceite de proposta online.
access_token via POST /oauth2/token.POST /v1/signing-sessions) com o PDF da proposta, perfil CLICK_ONLY e metadata contendo dealId e crmUrl.owner com o e-mail do vendedor — a SignDocs envia automaticamente o convite de aceite ao cliente.SIGNING_SESSION.COMPLETED e, ao recebê-lo, atualize o deal no CRM usando a metadata da transação.GET /v1/transactions/{transactionId}/download e /evidence) e anexe ao deal.Resultado: proposta aceita com registro probatório completo e CRM atualizado sem nenhum passo manual.
O aceite de proposta comercial é, na maioria das empresas, o elo mais lento do funil: o vendedor exporta o PDF, envia por e-mail, pede "um de acordo por escrito" e fica refém da caixa de entrada do cliente. Quando o aceite chega — às vezes como uma resposta vaga de e-mail, às vezes como um PDF impresso, assinado e fotografado — alguém ainda precisa atualizar o CRM à mão. Cada dia de espera é chance de o concorrente entrar, de o orçamento congelar ou de o decisor mudar.
Um fluxo de aceite de proposta online resolve as duas pontas. Para o cliente, aceitar a proposta vira um clique em uma página hospedada, no celular ou no desktop, sem cadastro e sem imprimir nada. Para o time de vendas, a assinatura eletrônica da proposta comercial gera evidência jurídica real (quem aceitou, quando, de qual IP, sobre qual versão exata do documento) — muito mais defensável que um "de acordo" por e-mail. E para o RevOps, a integração CRM + assinatura digital fecha o ciclo: o webhook de conclusão carrega os identificadores necessários para marcar o deal como fechado-ganho no segundo em que o cliente aceita.
Este guia monta esse fluxo de ponta a ponta com a API da SignDocs Brasil: deal ganho no CRM → sessão de assinatura criada com os dados do deal em metadata → cliente aceita → webhook devolve o controle ao seu sistema → CRM atualizado e evidência arquivada. Como bônus, mostramos como tratar alçada interna (desconto acima do limite precisa de aprovação do diretor) com uma sessão de confiança, sem gerar PDF nenhum.
CRM (deal em "Proposta") SignDocs Cliente
│ │ │
│ 1. POST /v1/signing-sessions │ │
│ metadata: dealId, crmUrl ─────>│ │
│ │ 2. e-mail de convite ─────────>│
│ │ (owner informado) │
│ │<─ 3. abre o link e aceita ─────│
│ │ (CLICK_ONLY) │
│<─ 4. webhook │ │
│ SIGNING_SESSION.COMPLETED │ │
│ │ │
│ 5. GET /v1/transactions/{id} ─────>│ │
│ lê metadata.dealId │ │
│ 6. marca deal "Fechado-ganho" │ │
│ 7. baixa PDF aceito + evidência ──>│ │
Por que cada primitiva neste cenário:
| Primitiva | Papel neste fluxo | Quando usar |
|---|---|---|
Sessão de assinatura (POST /v1/signing-sessions) |
Aceite da proposta pelo cliente | 1 signatário sobre 1 documento — o caso típico de proposta: quem aceita é o decisor do cliente |
Envelope (POST /v1/envelopes) |
Contrato definitivo pós-aceite | 2+ signatários no mesmo documento (cliente + representante legal da sua empresa); veja envelopes |
Sessão de confiança (POST /v1/trust-sessions) |
Alçada interna: aprovação do desconto pelo diretor | Autorizar uma ação sem documento — não faz sentido gerar PDF para um "aprovo o desconto de 25%"; veja aprovação |
transactions:read transactions:write evidence:read webhooks:write.npm install @signdocs-brasil/api (TypeScript) ou pip install signdocs-brasil (Python). Para Go, Java, PHP e C#, veja as receitas de assinatura expressa.🟠 HML vs Produção — desenvolva sempre em HML (
https://api-hml.signdocs.com.br, com hífen). Credenciais HML não consomem cota do plano e os dados expiram em 7 dias. Produção:https://api.signdocs.com.br.
A API usa OAuth2 client_credentials (servidor-a-servidor). Troque client_id + client_secret por um access_token de curta duração:
export SIGNDOCS_BASE_URL="https://api-hml.signdocs.com.br"
ACCESS_TOKEN=$(curl -s -X POST "$SIGNDOCS_BASE_URL/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=$SIGNDOCS_CLIENT_ID&client_secret=$SIGNDOCS_CLIENT_SECRET" \
| jq -r '.access_token')
import { SignDocsBrasilClient } from '@signdocs-brasil/api';
const client = new SignDocsBrasilClient({
clientId: process.env.SIGNDOCS_CLIENT_ID!,
clientSecret: process.env.SIGNDOCS_CLIENT_SECRET!,
baseUrl: process.env.SIGNDOCS_BASE_URL, // https://api-hml.signdocs.com.br
});
import os
from signdocs_brasil import SignDocsBrasilClient, ClientConfig
client = SignDocsBrasilClient(ClientConfig(
client_id=os.environ['SIGNDOCS_CLIENT_ID'],
client_secret=os.environ['SIGNDOCS_CLIENT_SECRET'],
base_url=os.environ.get('SIGNDOCS_BASE_URL', 'https://api-hml.signdocs.com.br'),
))
O SDK renova o token automaticamente. Em integrações HTTP puras, faça cache do token até perto do expires_in.
Este é o coração da integração. Quando o deal chega ao estágio "Proposta enviada" (ou quando o vendedor clica "Enviar para aceite"), seu sistema cria a sessão de assinatura. Dois campos fazem a ponte com o CRM:
metadata — chave-valor de strings (chaves até 256 chars, valores até 1024) que viaja com a transação; grave dealId e crmUrl para fechar o ciclo depois.expiresInMinutes — alinhe à validade da proposta (5 a 1440 minutos, ou seja, no máximo 24 horas por sessão; veja o callout abaixo para propostas com validade maior).Informar owner com os dados do vendedor faz a SignDocs enviar automaticamente o e-mail de convite ao cliente (quando signer.email for diferente de owner.email) e notificar o vendedor na conclusão. Se preferir entregar o link pelo seu próprio canal (e-mail transacional, WhatsApp Business do cliente via automação), omita owner.
PDF_BASE64=$(base64 -w0 proposta-8842.pdf)
curl -s -X POST "$SIGNDOCS_BASE_URL/v1/signing-sessions" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"purpose": "DOCUMENT_SIGNATURE",
"policy": { "profile": "CLICK_ONLY" },
"signer": {
"name": "João Andrade",
"email": "joao.andrade@clienteexemplo.com.br",
"cpf": "12345678901",
"userExternalId": "crm-contact-4415"
},
"owner": { "name": "Ana Vendas", "email": "ana@suaempresa.com.br" },
"document": { "content": "'"$PDF_BASE64"'", "filename": "proposta-8842-v3.pdf" },
"metadata": {
"dealId": "8842",
"crmUrl": "https://crm.suaempresa.com.br/deals/8842",
"proposalVersion": "v3",
"amountBRL": "48500.00"
},
"returnUrl": "https://suaempresa.com.br/proposta/obrigado",
"locale": "pt-BR",
"expiresInMinutes": 1440
}'
import { readFileSync } from 'fs';
const pdfBase64 = readFileSync('proposta-8842-v3.pdf').toString('base64');
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'CLICK_ONLY' },
signer: {
name: 'João Andrade',
email: 'joao.andrade@clienteexemplo.com.br',
cpf: '12345678901',
userExternalId: 'crm-contact-4415',
},
owner: { name: 'Ana Vendas', email: 'ana@suaempresa.com.br' },
document: { content: pdfBase64, filename: 'proposta-8842-v3.pdf' },
metadata: {
dealId: '8842',
crmUrl: 'https://crm.suaempresa.com.br/deals/8842',
proposalVersion: 'v3',
amountBRL: '48500.00',
},
returnUrl: 'https://suaempresa.com.br/proposta/obrigado',
locale: 'pt-BR',
expiresInMinutes: 1440, // proposta válida por 24h
});
console.log('Session ID:', session.sessionId);
console.log('Convite enviado pela SignDocs?', session.inviteSent);
import base64
with open('proposta-8842-v3.pdf', 'rb') as f:
pdf_base64 = base64.b64encode(f.read()).decode()
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='CLICK_ONLY'),
signer=Signer(
name='João Andrade',
email='joao.andrade@clienteexemplo.com.br',
cpf='12345678901',
user_external_id='crm-contact-4415',
),
owner=Owner(name='Ana Vendas', email='ana@suaempresa.com.br'),
document=InlineDocument(content=pdf_base64, filename='proposta-8842-v3.pdf'),
metadata={
'dealId': '8842',
'crmUrl': 'https://crm.suaempresa.com.br/deals/8842',
'proposalVersion': 'v3',
'amountBRL': '48500.00',
},
return_url='https://suaempresa.com.br/proposta/obrigado',
locale='pt-BR',
expires_in_minutes=1440, # proposta válida por 24h
))
print('Session ID:', session.session_id)
⚠️ Proposta válida por mais de 24 horas? —
expiresInMinutesaceita no máximo 1440 (24h). Para propostas com validade de vários dias, trate o eventoSIGNING_SESSION.EXPIREDno webhook: enquanto a data de validade da proposta não passou, crie uma nova sessão com o mesmo PDF e a mesmametadatae reenvie o link. Você mantém a validade comercial no seu sistema; a sessão é só a janela de aceite ativa.
✅ Dica — grave
sessionIdetransactionIdda resposta como campos customizados do deal no CRM. Junto com ametadata, isso dá rastreabilidade nos dois sentidos: do CRM para a SignDocs e da SignDocs para o CRM.
A resposta da criação traz url e clientSecret. O link que o cliente abre é a combinação dos dois — url sozinha não funciona:
# Da resposta do passo anterior:
# "url": "https://sign-hml.signdocs.com.br/s/01JXYZ...",
# "clientSecret": "ss_secret_eyJhbGciOi..."
SIGNING_LINK="${SESSION_URL}?cs=${CLIENT_SECRET}"
echo "$SIGNING_LINK"
const signingLink = `${session.url}?cs=${session.clientSecret}`;
// Se owner foi informado e session.inviteSent === true, a SignDocs já
// enviou o convite por e-mail. Use o link direto para canais adicionais:
// e-mail transacional próprio, SMS ou WhatsApp Business (via automação sua).
await crm.updateDeal('8842', { proposal_signing_link: signingLink });
signing_link = f"{session.url}?cs={session.client_secret}"
# Se owner foi informado e session.invite_sent for True, o convite por
# e-mail já saiu. Guarde o link no deal para reenvio manual pelo vendedor.
crm.update_deal('8842', {'proposal_signing_link': signing_link})
⚠️ O
clientSecreté sensível — quem tem o link completo consegue abrir a sessão de aceite. Não registre o link em logs nem o exponha em listagens do CRM visíveis a quem não é dono do deal.
Desconto acima de X% precisa do "de acordo" do diretor comercial — mas gerar um PDF só para isso é burocracia. Use uma sessão de confiança (POST /v1/trust-sessions): mesma mecânica de link hospedado e evidência, porém autenticando uma ação em vez de um documento. Só depois da aprovação interna o seu sistema cria a sessão de aceite do passo 5.
curl -s -X POST "$SIGNDOCS_BASE_URL/v1/trust-sessions" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": {
"type": "approve_discount",
"description": "Aprovar desconto de 25% na proposta #8842 (Cliente Exemplo Ltda, R$ 48.500,00)"
},
"policy": { "profile": "CLICK_PLUS_OTP" },
"signer": {
"name": "Ricardo Diretor",
"email": "ricardo@suaempresa.com.br",
"cpf": "98765432100",
"userExternalId": "employee-0007"
},
"metadata": {
"dealId": "8842",
"discountPercent": "25",
"requestedBy": "ana@suaempresa.com.br"
},
"expiresInMinutes": 240
}'
const approval = await fetch(`${process.env.SIGNDOCS_BASE_URL}/v1/trust-sessions`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
action: {
type: 'approve_discount',
description: 'Aprovar desconto de 25% na proposta #8842 (Cliente Exemplo Ltda, R$ 48.500,00)',
},
policy: { profile: 'CLICK_PLUS_OTP' },
signer: {
name: 'Ricardo Diretor',
email: 'ricardo@suaempresa.com.br',
cpf: '98765432100',
userExternalId: 'employee-0007',
},
metadata: {
dealId: '8842',
discountPercent: '25',
requestedBy: 'ana@suaempresa.com.br',
},
expiresInMinutes: 240,
}),
}).then((r) => r.json());
// Entregue approval.url + '?cs=' + approval.clientSecret ao diretor
approval = requests.post(
f"{os.environ['SIGNDOCS_BASE_URL']}/v1/trust-sessions",
headers={'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json'},
json={
'action': {
'type': 'approve_discount',
'description': 'Aprovar desconto de 25% na proposta #8842 (Cliente Exemplo Ltda, R$ 48.500,00)',
},
'policy': {'profile': 'CLICK_PLUS_OTP'},
'signer': {
'name': 'Ricardo Diretor',
'email': 'ricardo@suaempresa.com.br',
'cpf': '98765432100',
'userExternalId': 'employee-0007',
},
'metadata': {
'dealId': '8842',
'discountPercent': '25',
'requestedBy': 'ana@suaempresa.com.br',
},
'expiresInMinutes': 240,
},
).json()
# Entregue approval['url'] + '?cs=' + approval['clientSecret'] ao diretor
O diretor recebe o link, revisa a descrição da ação e confirma com clique + código OTP. O evento de conclusão libera o envio da proposta. O padrão completo (idempotência, consumo único da evidência, cancelamento) está no guia Sessão de Confiança — Aprovação.
Webhook é o caminho recomendado (próxima seção), mas o endpoint leve de status resolve consultas pontuais — por exemplo, um botão "Atualizar status" no card do deal:
curl -s "$SIGNDOCS_BASE_URL/v1/signing-sessions/$SESSION_ID/status" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# {"sessionId":"...","transactionId":"...","status":"COMPLETED","completedAt":"...","evidenceId":"ev_..."}
const status = await client.signingSessions.getStatus(session.sessionId);
console.log(status.status); // PENDING | COMPLETED | EXPIRED | CANCELLED
// Ou bloqueie até concluir (útil em scripts e testes):
const result = await client.signingSessions.waitForCompletion(session.sessionId);
console.log('Evidence ID:', result.evidenceId);
status = client.signing_sessions.get_status(session.session_id)
print(status.status) # PENDING | COMPLETED | EXPIRED | CANCELLED
# Ou bloqueie até concluir (útil em scripts e testes):
result = client.signing_sessions.wait_for_completion(session.session_id)
print('Evidence ID:', result.evidence_id)
Se a proposta for retirada (deal perdido, condições alteradas), cancele a sessão com POST /v1/signing-sessions/{sessionId}/cancel para invalidar o link imediatamente.
Após a conclusão, anexe ao deal duas coisas: o PDF aceito e o pacote de evidências — um JSON assinado em PKCS#7/CMS (.p7m) com certificado ICP-Brasil A1, contendo a trilha de auditoria, o hash SHA-256 do documento e os timestamps ISO do servidor.
# URLs pré-assinadas (expiram em 1 hora) do documento original e do assinado
curl -s "$SIGNDOCS_BASE_URL/v1/transactions/$TRANSACTION_ID/download" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Metadados da evidência + URL de download do .p7m
curl -s "$SIGNDOCS_BASE_URL/v1/transactions/$TRANSACTION_ID/evidence" \
-H "Authorization: Bearer $ACCESS_TOKEN"
const downloads = await client.transactions.getDownloadUrls(transactionId);
const evidence = await client.transactions.getEvidence(transactionId);
// Arquive junto ao deal: PDF aceito + evidence pack (.p7m)
await crm.attachFile('8842', downloads.signedDocumentUrl, 'proposta-aceita.pdf');
await crm.attachFile('8842', evidence.downloadUrl, 'evidencia-aceite.p7m');
downloads = client.transactions.get_download_urls(transaction_id)
evidence = client.transactions.get_evidence(transaction_id)
crm.attach_file('8842', downloads.signed_document_url, 'proposta-aceita.pdf')
crm.attach_file('8842', evidence.download_url, 'evidencia-aceite.p7m')
Qualquer pessoa pode conferir a autenticidade depois via GET /v1/verify/{evidenceId} — útil para auditoria ou disputa. Em HML, lembre que os dados expiram em 7 dias; em produção a evidência fica disponível de forma durável.
Registre um webhook para os eventos do ciclo de vida da proposta:
curl -s -X POST "$SIGNDOCS_BASE_URL/v1/webhooks" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://integracao.suaempresa.com.br/webhooks/signdocs",
"events": ["SIGNING_SESSION.COMPLETED", "SIGNING_SESSION.EXPIRED", "SIGNING_SESSION.CANCELLED"]
}'
O payload de SIGNING_SESSION.COMPLETED traz os identificadores da sessão e da transação:
{
"id": "del_01HX9Z7RABCDE4567890123",
"eventType": "SIGNING_SESSION.COMPLETED",
"tenantId": "tn_01HX9Z0ABCDEF1234567890",
"transactionId": "tx_01HX9Z3KQWERTY1234567890",
"timestamp": "2026-07-10T14:35:00.000Z",
"data": {
"sessionId": "ss_01HX9Z3KQWERTY1234567890",
"transactionId": "tx_01HX9Z3KQWERTY1234567890",
"status": "COMPLETED",
"evidenceId": "ev_01HX9Z4NFGHIJ0987654321",
"evidenceHash": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"completedAt": "2026-07-10T14:35:00.000Z"
}
}
Para recuperar a metadata com o dealId, consulte a transação a partir do transactionId do evento — é assim que o webhook "devolve" os dados do deal ao seu handler:
app.post('/webhooks/signdocs', async (req, res) => {
// 1. Valide a assinatura HMAC-SHA256 (headers X-SignDocs-Signature +
// X-SignDocs-Timestamp) ANTES de processar — veja ./webhooks.html
const event = req.body;
if (event.eventType === 'SIGNING_SESSION.COMPLETED') {
// 2. A metadata vive na transação — busque-a pelo transactionId do evento
const tx = await client.transactions.get(event.data.transactionId);
const { dealId, crmUrl } = tx.metadata;
// 3. Feche o ciclo no CRM
await crm.updateDeal(dealId, {
stage: 'closed_won',
accepted_at: event.data.completedAt,
evidence_id: event.data.evidenceId,
});
}
if (event.eventType === 'SIGNING_SESSION.EXPIRED') {
const tx = await client.transactions.get(event.data.transactionId);
await crm.addDealNote(tx.metadata.dealId, 'Link de aceite expirou — reemitir se a proposta ainda vale.');
}
res.status(200).end(); // responda 2xx rápido; processe pesado de forma assíncrona
});
@app.post('/webhooks/signdocs')
def signdocs_webhook():
# 1. Valide a assinatura HMAC-SHA256 (X-SignDocs-Signature +
# X-SignDocs-Timestamp) ANTES de processar — veja ./webhooks.html
event = request.get_json()
if event['eventType'] == 'SIGNING_SESSION.COMPLETED':
# 2. A metadata vive na transação — busque-a pelo transactionId do evento
tx = client.transactions.get(event['data']['transactionId'])
deal_id = tx.metadata['dealId']
# 3. Feche o ciclo no CRM
crm.update_deal(deal_id, {
'stage': 'closed_won',
'accepted_at': event['data']['completedAt'],
'evidence_id': event['data']['evidenceId'],
})
return '', 200 # responda 2xx rápido; processe pesado de forma assíncrona
A entrega é at-least-once: torne o handler idempotente (marcar um deal já fechado como fechado deve ser inofensivo). A especificação completa da validação HMAC, retries e replay está em Webhooks.
| Perfil | Fricção para o cliente | Quando usar em propostas |
|---|---|---|
CLICK_ONLY |
Mínima — um clique | Default recomendado. Aceite de proposta entre partes que já se conhecem e negociaram; validade pela manifestação de vontade (MP 2.200-2, art. 10, §2º) |
CLICK_PLUS_OTP |
Baixa — clique + código por e-mail/SMS | Propostas de alto valor ou cliente novo: o OTP amarra o aceite à posse do e-mail/telefone do decisor, fortalecendo a prova de autoria |
BIOMETRIC |
Média — verificação facial | Setores com risco elevado de repúdio ou exigência interna de identificação forte (requer signer.cpf) |
DIGITAL_CERTIFICATE |
Alta — exige certificado ICP-Brasil | Quando o contrato definitivo (não a proposta) exigir assinatura com certificado ICP-Brasil; raro na fase de aceite |
Comece com CLICK_ONLY: no aceite comercial, cada camada extra de autenticação custa conversão, e a evidência gerada (IP, user-agent, timestamps do servidor, hash do documento, trilha de eventos) já sustenta o consentimento. Suba para CLICK_PLUS_OTP quando o valor do deal justificar a fricção — é uma troca de um campo no payload, sem mudança de integração. Detalhes jurídicos de cada nível: Perfis de assinatura e níveis legais.
Seu time de RevOps consegue montar esse fluxo sem backend próprio:
n8n-nodes-signdocs-brasil para fluxos self-hosted; veja Integração n8n.Todos os erros seguem RFC 7807 (application/problem+json).
1. Perfil de política inválido (400) — DIGITAL_SIGN_A1 é um tipo de etapa, não um perfil.
{ "type": ".../errors/bad-request", "title": "Bad Request", "status": 400,
"detail": "policy.profile must be one of CLICK_ONLY, CLICK_PLUS_OTP, BIOMETRIC, BIOMETRIC_PLUS_OTP, DIGITAL_CERTIFICATE, CUSTOM" }
Correção: use { "policy": { "profile": "CLICK_ONLY" } }.
2. Signatário sem CPF/CNPJ (400) — o signer exige name, userExternalId e pelo menos um entre cpf e cnpj.
{ "title": "Bad Request", "status": 400,
"detail": "signer must include at least one of: cpf, cnpj" }
Correção: para aceite por pessoa jurídica, envie o cnpj do cliente; para o decisor pessoa física, o cpf (11 dígitos, sem pontuação).
3. Expiração fora do intervalo (400) — tentou expiresInMinutes: 10080 para uma proposta de 7 dias.
{ "title": "Bad Request", "status": 400,
"detail": "expiresInMinutes must be between 5 and 1440" }
Correção: use até 1440 e reemita a sessão via handler de SIGNING_SESSION.EXPIRED (callout do passo 5).
4. Metadata estourando limites (400) — colocou o JSON inteiro da proposta em um valor de metadata.
{ "title": "Bad Request", "status": 400,
"detail": "metadata values must be strings of at most 1024 characters" }
Correção: grave apenas identificadores e referências (dealId, crmUrl, versão, valor); o documento em si já viaja em document.
5. Link "sessão inválida" para o cliente — você entregou session.url sem o clientSecret. O link correto é url + "?cs=" + clientSecret. Se o secret se perdeu (é retornado uma única vez), cancele a sessão e crie outra.
Sim. A MP 2.200-2/2001 (art. 10, §2º) reconhece assinaturas eletrônicas quando as partes admitem o meio como válido — e o aceite de proposta é exatamente uma manifestação de vontade entre partes que negociaram. A SignDocs registra IP, user-agent, timestamps ISO do servidor, hash SHA-256 do documento e a trilha completa de eventos em um pacote de evidências assinado com certificado ICP-Brasil, o que sustenta a prova em caso de disputa.
Pelo webhook SIGNING_SESSION.COMPLETED. O evento traz o transactionId; seu handler consulta GET /v1/transactions/{transactionId}, lê metadata.dealId (que você gravou na criação) e atualiza o deal. Nada de planilha nem conferência manual.
Você escolhe. Informando owner na criação (com e-mail diferente do signatário), a SignDocs envia o convite automaticamente e o campo inviteSent da resposta confirma. Sem owner, você entrega o link (url + ?cs= + clientSecret) pelo seu canal — e-mail transacional próprio, SMS ou WhatsApp via sua automação.
A sessão vira EXPIRED e o link deixa de funcionar; você recebe o webhook SIGNING_SESSION.EXPIRED. Se a validade comercial da proposta ainda não passou, crie uma nova sessão com o mesmo PDF e a mesma metadata e reenvie — a validade da proposta é regra sua, a sessão é só a janela de aceite ativa (máx. 24h cada).
Sim — e é o próximo passo natural. Se o contrato exige múltiplos signatários (cliente + representante legal da sua empresa), use um envelope (POST /v1/envelopes) com modo sequencial ou paralelo e baixe o PDF final com o carimbo combinado. Veja Envelopes de assinatura expressa.
Use uma sessão de confiança: o diretor aprova a ação "conceder 25% de desconto no deal 8842" com clique + OTP, sem PDF, e a evidência fica arquivada. Só depois disso seu sistema dispara a sessão de aceite ao cliente. Guia completo: Sessão de Confiança — Aprovação.