Digitalize a homologação de fornecedores de ponta a ponta: verifique a identidade do representante legal (KYC), assine o contrato de fornecimento online com validade jurídica fundada na MP 2.200-2/2001 e arquive um pacote de evidências assinado com certificado ICP-Brasil — tudo por API, integrado ao seu ERP e em conformidade com a LGPD. O resultado: cadastro de fornecedores com assinatura eletrônica em horas, não semanas, com trilha de auditoria completa para o compliance de compras.
Público-alvo: times de compras/procurement e desenvolvedores que integram ERPs (SAP, TOTVS, Oracle, sistemas próprios) ao processo de qualificação e contratação de fornecedores.
client_credentials em POST /oauth2/token.POST /v1/trust-sessions) para o representante legal do fornecedor, com metadata.supplierId, e envie o link de verificação.TRANSACTION.COMPLETED, crie o envelope do contrato de fornecimento (POST /v1/envelopes) com fornecedor + comprador e entregue os links de assinatura.ENVELOPE.ALL_SIGNED, baixe o PDF com carimbo combinado e o pacote de evidências — fornecedor homologado, contrato assinado e arquivado.A homologação de fornecedores tradicional é um funil lento: o time de compras coleta contrato social, procurações e documentos do representante legal por e-mail, confere tudo manualmente, imprime o contrato de fornecimento, envia para assinatura física (ou coleta assinaturas escaneadas sem qualquer verificação de identidade) e só então libera o cadastro no ERP. Cada etapa manual adiciona dias ao lead time — e fornecedores estratégicos ficam parados na fila enquanto a operação precisa deles ativos.
Pior: o processo em papel raramente responde à pergunta que o compliance faz depois — quem assinou o contrato de fornecimento era mesmo o representante legal daquela empresa? Uma assinatura escaneada colada num PDF não prova identidade, não gera trilha de auditoria e não resiste a uma disputa contratual ou a uma auditoria de fraude em cadeia de suprimentos.
A homologação de fornecedores digital resolve as duas pontas com um fluxo em duas fases: primeiro uma verificação de identidade do representante legal (KYC via sessão de confiança, com biometria facial opcional e CPF/CNPJ validados no fluxo), depois o contrato de fornecimento online assinado eletronicamente por fornecedor e comprador em um envelope. As duas fases ficam correlacionadas pelo metadata (ex.: supplierId do seu ERP), e cada uma gera um pacote de evidências independente com hash SHA-256, timestamp ISO do servidor e trilha de auditoria, assinado com certificado ICP-Brasil.
ERP / Sistema de Compras
│
│ FASE 1 — verificação do representante legal
│ POST /v1/trust-sessions (metadata.supplierId = forn-8842)
▼
Representante legal ──verifica identidade──▶ webhook TRANSACTION.COMPLETED
│ │
│ FASE 2 — contrato de fornecimento │ (seu backend cria o envelope)
│ POST /v1/envelopes (metadata.supplierId) │
│ POST /v1/envelopes/{id}/sessions × 2 │
▼ ▼
Fornecedor assina ──▶ Comprador assina ──▶ webhook ENVELOPE.ALL_SIGNED
│
▼
Fornecedor HOMOLOGADO no ERP
(carimbo combinado + pacotes de evidências arquivados)
Por que cada primitiva neste cenário:
| Primitiva | Papel no fluxo | Por quê |
|---|---|---|
Sessão de confiança (/v1/trust-sessions) |
Fase 1 — KYC do representante legal | Autentica uma ação sem documento: prova quem é a pessoa antes de existir contrato. Veja Sessão de Confiança para KYC. |
Envelope (/v1/envelopes) |
Fase 2 — contrato de fornecimento | O contrato tem 2+ signatários (fornecedor e comprador) sobre o mesmo PDF, com carimbo combinado ao final. |
Sessão de assinatura avulsa (/v1/signing-sessions) |
Não usada aqui | Serve para 1 signatário sobre 1 documento — insuficiente para um contrato bilateral. |
transactions:read transactions:write steps:write evidence:read webhooks:write.🟠 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.
Troque client_id + client_secret por um access_token de curta duração via client_credentials:
ACCESS_TOKEN=$(curl -s -X POST "https://api-hml.signdocs.com.br/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')
const BASE = process.env.SIGNDOCS_BASE_URL ?? 'https://api-hml.signdocs.com.br';
async function getAccessToken(): Promise<string> {
const resp = await fetch(`${BASE}/oauth2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.SIGNDOCS_CLIENT_ID!,
client_secret: process.env.SIGNDOCS_CLIENT_SECRET!,
}),
});
if (!resp.ok) throw new Error(`Token error: ${resp.status}`);
const { access_token } = await resp.json();
return access_token;
}
import os, requests
BASE = os.environ.get("SIGNDOCS_BASE_URL", "https://api-hml.signdocs.com.br")
def get_access_token() -> str:
resp = requests.post(
f"{BASE}/oauth2/token",
data={
"grant_type": "client_credentials",
"client_id": os.environ["SIGNDOCS_CLIENT_ID"],
"client_secret": os.environ["SIGNDOCS_CLIENT_SECRET"],
},
timeout=10,
)
resp.raise_for_status()
return resp.json()["access_token"]
Cache o token até perto do expires_in e renove — não peça um token novo por requisição.
Antes de qualquer contrato, prove que a pessoa que representa o fornecedor é quem diz ser. A sessão de confiança autentica essa ação de verificação sem documento anexado: o representante recebe um link, passa pelo fluxo de identidade e o resultado vira um pacote de evidências auditável. O signer leva o CPF do representante; o CNPJ do fornecedor e o supplierId do seu ERP vão no metadata — é essa chave que encadeia a Fase 2.
O guia dedicado Sessão de Confiança para KYC cobre o fluxo em profundidade; para KYC de clientes finais (pessoa física em escala), veja o modelo KYC de Clientes.
curl -s -X POST "https://api-hml.signdocs.com.br/v1/trust-sessions" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: kyc-forn-8842-v1" \
-d '{
"action": {
"type": "supplier_kyc_verification",
"description": "Verificação de identidade do representante legal — homologação do fornecedor forn-8842 (Metalúrgica Aurora Ltda)"
},
"policy": { "profile": "BIOMETRIC" },
"signer": {
"name": "Carlos Pereira",
"cpf": "12345678901",
"email": "carlos@metalurgicaaurora.com.br",
"userExternalId": "forn-8842-rep",
"otpChannel": "email"
},
"metadata": {
"supplierId": "forn-8842",
"supplierCnpj": "12345678000199",
"erpVendorRecord": "SAP-VENDOR-004421"
},
"returnUrl": "https://compras.suaempresa.com.br/fornecedores/forn-8842/verificado",
"cancelUrl": "https://compras.suaempresa.com.br/fornecedores/forn-8842/cancelado",
"locale": "pt-BR",
"expiresInMinutes": 4320
}'
A resposta traz sessionId, url e clientSecret (one-time). O link entregue ao representante é url + ?cs= + clientSecret.
async function iniciarVerificacaoFornecedor(fornecedor: {
id: string;
cnpj: string;
representante: { nome: string; cpf: string; email: string };
}) {
const token = await getAccessToken();
const resp = await fetch(`${BASE}/v1/trust-sessions`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'X-Idempotency-Key': `kyc-${fornecedor.id}-v1`,
},
body: JSON.stringify({
action: {
type: 'supplier_kyc_verification',
description: `Verificação de identidade do representante legal — homologação do fornecedor ${fornecedor.id}`,
},
policy: { profile: 'BIOMETRIC' },
signer: {
name: fornecedor.representante.nome,
cpf: fornecedor.representante.cpf,
email: fornecedor.representante.email,
userExternalId: `${fornecedor.id}-rep`,
otpChannel: 'email',
},
metadata: { supplierId: fornecedor.id, supplierCnpj: fornecedor.cnpj },
returnUrl: `https://compras.suaempresa.com.br/fornecedores/${fornecedor.id}/verificado`,
cancelUrl: `https://compras.suaempresa.com.br/fornecedores/${fornecedor.id}/cancelado`,
locale: 'pt-BR',
expiresInMinutes: 4320,
}),
});
if (!resp.ok) throw new Error(`SignDocs ${resp.status}: ${await resp.text()}`);
const { sessionId, url, clientSecret } = await resp.json();
// Guarde a correlação sessionId -> supplierId para o webhook da Fase 2
await db.fornecedores.update(fornecedor.id, {
kycSessionId: sessionId,
status: 'KYC_PENDENTE',
});
return `${url}?cs=${clientSecret}`; // entregue este link ao representante
}
def iniciar_verificacao_fornecedor(fornecedor: dict) -> str:
token = get_access_token()
resp = requests.post(
f"{BASE}/v1/trust-sessions",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"X-Idempotency-Key": f"kyc-{fornecedor['id']}-v1",
},
json={
"action": {
"type": "supplier_kyc_verification",
"description": (
"Verificação de identidade do representante legal — "
f"homologação do fornecedor {fornecedor['id']}"
),
},
"policy": {"profile": "BIOMETRIC"},
"signer": {
"name": fornecedor["representante"]["nome"],
"cpf": fornecedor["representante"]["cpf"],
"email": fornecedor["representante"]["email"],
"userExternalId": f"{fornecedor['id']}-rep",
"otpChannel": "email",
},
"metadata": {
"supplierId": fornecedor["id"],
"supplierCnpj": fornecedor["cnpj"],
},
"returnUrl": f"https://compras.suaempresa.com.br/fornecedores/{fornecedor['id']}/verificado",
"cancelUrl": f"https://compras.suaempresa.com.br/fornecedores/{fornecedor['id']}/cancelado",
"locale": "pt-BR",
"expiresInMinutes": 4320,
},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
# Guarde a correlação sessionId -> supplierId para o webhook da Fase 2
db.fornecedores.update(
fornecedor["id"],
kyc_session_id=data["sessionId"],
status="KYC_PENDENTE",
)
return f"{data['url']}?cs={data['clientSecret']}" # link do representante
✅ Dica —
BIOMETRICna verificação exige habilitação de biometria no seu tenant. Se ainda não tiver, comece compolicy.profile: "CLICK_PLUS_OTP"na Fase 1 e faça o upgrade depois: o restante do fluxo não muda.
⚠️ Idempotência — derive a
X-Idempotency-Keyde uma chave de negócio estável (ex.:kyc-{supplierId}-v1), não de um UUID aleatório. Assim, retries após timeout não criam sessões duplicadas para o mesmo fornecedor.
Quando o representante conclui a verificação, o SignDocs envia TRANSACTION.COMPLETED ao seu webhook. Seu handler correlaciona o sessionId com o supplierId guardado na Fase 1 e cria o envelope do contrato de fornecimento — repetindo o mesmo metadata.supplierId (e anexando o kycEvidenceId) para que as duas fases fiquem amarradas de ponta a ponta na sua auditoria.
O contrato usa signingMode: "SEQUENTIAL": o fornecedor (signatário 1, assinando como pessoa jurídica via cnpj) assina primeiro; o representante de compras (signatário 2, via cpf) só consegue assinar depois. Documento inline em base64, máximo 10 MB.
PDF_BASE64=$(base64 -w0 contrato-fornecimento-forn-8842.pdf)
# 1. Criar o envelope
ENVELOPE_ID=$(curl -s -X POST "https://api-hml.signdocs.com.br/v1/envelopes" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: contrato-forn-8842-v1" \
-d "{
\"signingMode\": \"SEQUENTIAL\",
\"totalSigners\": 2,
\"document\": { \"content\": \"$PDF_BASE64\", \"filename\": \"contrato-fornecimento-forn-8842.pdf\" },
\"metadata\": {
\"supplierId\": \"forn-8842\",
\"kycEvidenceId\": \"evd_01JKYC...\",
\"contractType\": \"fornecimento\"
},
\"owner\": { \"email\": \"compras@suaempresa.com.br\", \"name\": \"Equipe de Compras\" },
\"returnUrl\": \"https://compras.suaempresa.com.br/fornecedores/forn-8842/contrato\",
\"locale\": \"pt-BR\",
\"expiresInMinutes\": 10080
}" | jq -r '.envelopeId')
# 2. Signatário 1 — fornecedor (assina como PJ, via CNPJ)
curl -s -X POST "https://api-hml.signdocs.com.br/v1/envelopes/$ENVELOPE_ID/sessions" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"signer": {
"name": "Carlos Pereira",
"email": "carlos@metalurgicaaurora.com.br",
"cnpj": "12345678000199",
"userExternalId": "forn-8842-rep"
},
"policy": { "profile": "CLICK_PLUS_OTP" },
"purpose": "DOCUMENT_SIGNATURE",
"signerIndex": 1
}'
# 3. Signatário 2 — comprador
curl -s -X POST "https://api-hml.signdocs.com.br/v1/envelopes/$ENVELOPE_ID/sessions" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"signer": {
"name": "Ana Ribeiro",
"email": "ana.ribeiro@suaempresa.com.br",
"cpf": "98765432100",
"userExternalId": "compras-ana"
},
"policy": { "profile": "CLICK_PLUS_OTP" },
"purpose": "DOCUMENT_SIGNATURE",
"signerIndex": 2
}'
Cada chamada de sessão retorna url + clientSecret — monte o link como url?cs={clientSecret}.
// Handler do webhook (Express) — valide a assinatura HMAC antes: ./webhooks.html
app.post('/webhooks/signdocs', async (req, res) => {
const { event, data } = req.body;
if (event === 'TRANSACTION.COMPLETED') {
const fornecedor = await db.fornecedores.findByKycSession(data.sessionId);
if (fornecedor && fornecedor.status === 'KYC_PENDENTE') {
await criarEnvelopeContrato(fornecedor, data.evidenceId);
}
}
res.status(200).end();
});
async function criarEnvelopeContrato(fornecedor: Fornecedor, kycEvidenceId: string) {
const token = await getAccessToken();
const pdfBase64 = (await gerarContratoPdf(fornecedor)).toString('base64');
const envResp = await fetch(`${BASE}/v1/envelopes`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'X-Idempotency-Key': `contrato-${fornecedor.id}-v1`,
},
body: JSON.stringify({
signingMode: 'SEQUENTIAL',
totalSigners: 2,
document: { content: pdfBase64, filename: `contrato-fornecimento-${fornecedor.id}.pdf` },
metadata: { supplierId: fornecedor.id, kycEvidenceId, contractType: 'fornecimento' },
owner: { email: 'compras@suaempresa.com.br', name: 'Equipe de Compras' },
returnUrl: `https://compras.suaempresa.com.br/fornecedores/${fornecedor.id}/contrato`,
locale: 'pt-BR',
expiresInMinutes: 10080,
}),
});
const { envelopeId } = await envResp.json();
const addSession = (body: object) =>
fetch(`${BASE}/v1/envelopes/${envelopeId}/sessions`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}).then((r) => r.json());
const s1 = await addSession({
signer: {
name: fornecedor.representante.nome,
email: fornecedor.representante.email,
cnpj: fornecedor.cnpj,
userExternalId: `${fornecedor.id}-rep`,
},
policy: { profile: 'CLICK_PLUS_OTP' },
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 1,
});
const s2 = await addSession({
signer: { name: 'Ana Ribeiro', email: 'ana.ribeiro@suaempresa.com.br', cpf: '98765432100', userExternalId: 'compras-ana' },
policy: { profile: 'CLICK_PLUS_OTP' },
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 2,
});
await db.fornecedores.update(fornecedor.id, {
status: 'CONTRATO_PENDENTE',
envelopeId,
linkFornecedor: `${s1.url}?cs=${s1.clientSecret}`,
linkComprador: `${s2.url}?cs=${s2.clientSecret}`,
});
}
# Handler do webhook (Flask) — valide a assinatura HMAC antes: ./webhooks.html
@app.post("/webhooks/signdocs")
def signdocs_webhook():
body = request.get_json()
event, data = body["event"], body["data"]
if event == "TRANSACTION.COMPLETED":
fornecedor = db.fornecedores.find_by_kyc_session(data["sessionId"])
if fornecedor and fornecedor.status == "KYC_PENDENTE":
criar_envelope_contrato(fornecedor, data["evidenceId"])
return "", 200
def criar_envelope_contrato(fornecedor, kyc_evidence_id: str):
token = get_access_token()
pdf_base64 = gerar_contrato_pdf_base64(fornecedor)
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
env = requests.post(
f"{BASE}/v1/envelopes",
headers={**headers, "X-Idempotency-Key": f"contrato-{fornecedor.id}-v1"},
json={
"signingMode": "SEQUENTIAL",
"totalSigners": 2,
"document": {"content": pdf_base64, "filename": f"contrato-fornecimento-{fornecedor.id}.pdf"},
"metadata": {"supplierId": fornecedor.id, "kycEvidenceId": kyc_evidence_id, "contractType": "fornecimento"},
"owner": {"email": "compras@suaempresa.com.br", "name": "Equipe de Compras"},
"returnUrl": f"https://compras.suaempresa.com.br/fornecedores/{fornecedor.id}/contrato",
"locale": "pt-BR",
"expiresInMinutes": 10080,
},
timeout=10,
).json()
def add_session(payload):
return requests.post(
f"{BASE}/v1/envelopes/{env['envelopeId']}/sessions",
headers=headers, json=payload, timeout=10,
).json()
s1 = add_session({
"signer": {
"name": fornecedor.representante.nome,
"email": fornecedor.representante.email,
"cnpj": fornecedor.cnpj,
"userExternalId": f"{fornecedor.id}-rep",
},
"policy": {"profile": "CLICK_PLUS_OTP"},
"purpose": "DOCUMENT_SIGNATURE",
"signerIndex": 1,
})
s2 = add_session({
"signer": {"name": "Ana Ribeiro", "email": "ana.ribeiro@suaempresa.com.br",
"cpf": "98765432100", "userExternalId": "compras-ana"},
"policy": {"profile": "CLICK_PLUS_OTP"},
"purpose": "DOCUMENT_SIGNATURE",
"signerIndex": 2,
})
db.fornecedores.update(
fornecedor.id,
status="CONTRATO_PENDENTE",
envelope_id=env["envelopeId"],
link_fornecedor=f"{s1['url']}?cs={s1['clientSecret']}",
link_comprador=f"{s2['url']}?cs={s2['clientSecret']}",
)
owner.email diferente dos e-mails dos signatários, a API envia automaticamente o e-mail de convite a cada signatário. Se você omitir owner, a entrega do link (url?cs={clientSecret}) fica por sua conta — e-mail transacional do seu ERP, portal do fornecedor, ou WhatsApp/SMS via seus próprios canais.SEQUENTIAL, o comprador só consegue assinar depois que o fornecedor concluir — a página hospedada informa "aguardando signatário anterior" se acessada antes da vez.GET /v1/envelopes/{envelopeId} retorna o status do envelope e das sessões. Prefira webhooks a polling — mais reativo e mais barato em cota.⚠️ O
clientSecreté one-time e não é armazenado — guarde o link montado no seu banco no momento da criação. Se perder, cancele a sessão e crie outra.
Quando o segundo signatário conclui, chega o webhook ENVELOPE.ALL_SIGNED. É o gatilho para marcar o fornecedor como homologado no ERP e arquivar os artefatos finais:
# PDF final com carimbo combinado de todas as assinaturas
curl -s -X POST "https://api-hml.signdocs.com.br/v1/envelopes/$ENVELOPE_ID/combined-stamp" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-o contrato-fornecimento-assinado.pdf
# Pacote de evidências (JSON assinado em PKCS#7/CMS, certificado ICP-Brasil A1)
curl -s "https://api-hml.signdocs.com.br/v1/evidence/$EVIDENCE_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-o pacote-evidencias-forn-8842.p7m
// No mesmo handler de webhook:
if (event === 'ENVELOPE.ALL_SIGNED') {
const fornecedor = await db.fornecedores.findByEnvelope(data.envelopeId);
const token = await getAccessToken();
const stamp = await fetch(`${BASE}/v1/envelopes/${data.envelopeId}/combined-stamp`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
await salvarNoArquivo(fornecedor.id, 'contrato-assinado.pdf', await stamp.arrayBuffer());
await db.fornecedores.update(fornecedor.id, { status: 'HOMOLOGADO' });
await erp.liberarCadastroFornecedor(fornecedor.id); // libera pedidos de compra
}
# No mesmo handler de webhook:
if event == "ENVELOPE.ALL_SIGNED":
fornecedor = db.fornecedores.find_by_envelope(data["envelopeId"])
token = get_access_token()
stamp = requests.post(
f"{BASE}/v1/envelopes/{data['envelopeId']}/combined-stamp",
headers={"Authorization": f"Bearer {token}"}, timeout=30,
)
salvar_no_arquivo(fornecedor.id, "contrato-assinado.pdf", stamp.content)
db.fornecedores.update(fornecedor.id, status="HOMOLOGADO")
erp.liberar_cadastro_fornecedor(fornecedor.id) # libera pedidos de compra
A prova de integridade de cada fase = timestamp ISO do servidor + trilha de auditoria + hash SHA-256 do documento + pacote de evidências assinado com certificado ICP-Brasil. Qualquer parte pode verificar depois via GET /v1/verify/{evidenceId} ou reenviando o PDF em POST /v1/verify/document. Detalhes do pacote: evidências de sessão de confiança.
Registre um único endpoint para os eventos das duas fases:
curl -s -X POST "https://api-hml.signdocs.com.br/v1/webhooks" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://erp.suaempresa.com.br/webhooks/signdocs",
"events": [
"TRANSACTION.COMPLETED",
"TRANSACTION.FAILED",
"ENVELOPE.ALL_SIGNED",
"SIGNING_SESSION.EXPIRED"
]
}'
Papel de cada evento neste fluxo:
| Evento | O que significa aqui | Ação do seu backend |
|---|---|---|
TRANSACTION.COMPLETED |
Fase 1 concluída — representante verificado | Criar o envelope do contrato (Fase 2) |
TRANSACTION.FAILED |
Verificação reprovada ou falhou | Marcar fornecedor para revisão manual |
ENVELOPE.ALL_SIGNED |
Contrato assinado por ambos | Homologar fornecedor, baixar carimbo + evidências |
SIGNING_SESSION.EXPIRED |
Um signatário não assinou no prazo | Notificar compras, reemitir sessão |
Todo payload vem assinado com HMAC-SHA256 — valide a assinatura e o timestamp antes de processar, e trate entregas duplicadas (at-least-once) com idempotência no consumo. Especificação completa, headers e código de validação: guia de webhooks e webhooks com SDK.
| Perfil | Como funciona | Quando usar neste cenário |
|---|---|---|
CLICK_PLUS_OTP |
Aceite + código OTP por e-mail/SMS | Recomendado para o contrato de fornecimento — verificação de posse do canal com fricção baixa, adequado a um fluxo B2B onde a identidade já foi provada na Fase 1 |
BIOMETRIC |
Verificação facial na página hospedada | Opcional na Fase 1 (KYC) — eleva a garantia de identidade do representante; requer biometria habilitada no tenant |
CLICK_ONLY |
Apenas aceite com um clique | Aditivos de baixo risco entre partes já homologadas; evite no contrato principal |
DIGITAL_CERTIFICATE |
Assinatura com certificado ICP-Brasil (e-CPF/e-CNPJ) | Quando a política de compras ou o contrato exigir assinatura com certificado digital |
Default recomendado: BIOMETRIC (ou CLICK_PLUS_OTP) na verificação da Fase 1 e CLICK_PLUS_OTP no contrato da Fase 2 — a combinação prova identidade uma vez e mantém a assinatura do contrato fluida. Comparativo completo de níveis de garantia e enquadramento legal: perfis de assinatura e níveis legais.
O fluxo inteiro roda no Make.com sem escrever backend, ligando seu ERP/planilha de fornecedores ao SignDocs:
supplierId no metadata) → módulo de e-mail entrega {{url}}?cs={{clientSecret}} ao representante.TRANSACTION.COMPLETED → Criar Envelope + 2× Adicionar Sessão ao Envelope → e-mails com os links de assinatura.ENVELOPE.ALL_SIGNED → Baixar Carimbo Combinado → atualiza o registro do fornecedor no ERP/CRM e arquiva o PDF no Drive.Passo a passo do app e da conexão OAuth2: integração Make.com. O mesmo desenho funciona em n8n e Zapier.
1. Signer sem CPF nem CNPJ (400)
{ "status": 400, "title": "Validation Error", "detail": "signer: é obrigatório informar pelo menos um entre 'cpf' e 'cnpj'" }
Correção: envie cpf (11 dígitos, sem pontuação) para pessoa física ou cnpj (14 dígitos) para o fornecedor assinando como PJ.
2. DIGITAL_SIGN_A1 usado como perfil de política (400)
{ "status": 400, "title": "Validation Error", "detail": "policy.profile: valor inválido 'DIGITAL_SIGN_A1'" }
Correção: o perfil correto é DIGITAL_CERTIFICATE. DIGITAL_SIGN_A1 aparece apenas como tipo de step nas respostas, nunca como policy.profile na requisição.
3. Webhook recusado na borda (403 CloudFront)
{ "status": 403, "title": "Forbidden", "detail": "webhook url rejected at edge" }
Correção: a URL do webhook aponta para localhost ou IP privado — o registro exige HTTPS público. Em desenvolvimento, use um túnel (ngrok/cloudflared) ou webhook.site.
4. Token expirado (401)
{ "status": 401, "title": "Unauthorized", "detail": "access token inválido ou expirado" }
Correção: renove o token via POST /oauth2/token antes do expires_in e verifique se as credenciais correspondem ao ambiente da URL (HML vs produção).
5. Envelope não encontrado em HML (404)
{ "status": 404, "title": "Not Found", "detail": "envelope não encontrado" }
Correção: em HML os dados expiram em 7 dias. Se o teste ficou parado além disso, recrie os recursos — em produção o histórico permanece consultável.
Sim. O contrato de fornecimento assinado eletronicamente tem validade fundada na MP 2.200-2/2001, art. 10 §2º, pelo consentimento das partes quanto ao meio de assinatura — e o pacote de evidências (hash SHA-256, trilha de auditoria, timestamp do servidor) materializa essa prova. Para os níveis de garantia por perfil, veja perfis de assinatura e níveis legais.
Não. Com CLICK_PLUS_OTP o representante assina com aceite + código OTP, sem certificado. O perfil DIGITAL_CERTIFICATE fica disponível quando sua política de compras exigir assinatura com e-CPF/e-CNPJ — basta trocar o policy.profile da sessão do fornecedor.
Integre por API (este guia) usando metadata.supplierId como chave de correlação entre o registro do fornecedor no ERP e os recursos SignDocs, ou monte o fluxo sem código no Make/n8n/Zapier a partir de um trigger do próprio ERP. Os webhooks devolvem os eventos para o ERP atualizar o status do fornecedor automaticamente.
Você recebe TRANSACTION.FAILED no webhook e o envelope da Fase 2 nunca é criado — o fornecedor fica retido para análise manual do time de compras. Nenhum contrato é enviado a um representante cuja identidade não foi confirmada.
Sim. O envelope aceita totalSigners ≥ 2 — inclua testemunhas ou um segundo aprovador de compras adicionando mais sessões com signerIndex sequencial. Em SEQUENTIAL, a ordem dos índices define a ordem de assinatura.
Cada fase gera um pacote de evidências JSON assinado em PKCS#7/CMS com certificado ICP-Brasil A1, incluindo o hash SHA-256 do documento. Verifique a qualquer momento via GET /v1/verify/{evidenceId} ou reenviando o PDF em POST /v1/verify/document.