Colocar assinatura eletrônica no Salesforce parece simples: gera o PDF, manda pra API, recebe o status de volta por webhook. Na prática, é no webhook que quase toda integração trava — e sempre pelos mesmos motivos. Este guia mostra as 4 armadilhas reais (com o código para resolver cada uma), válidas para ClickSign, DocuSign, D4Sign, Autentique e qualquer plataforma que devolve status por callback.
O fluxo em 3 pernas (e onde ele quebra)
Toda integração de assinatura tem a mesma anatomia: gerar o documento (o passo de gerar o PDF da proposta a partir da Oportunidade, via Visualforce), enviar para assinatura (callouts para a API do provedor) e receber a atualização de status por um webhook público. As duas primeiras pernas quase nunca dão problema. A terceira — o webhook chegando de fora para dentro do Salesforce — é onde o modelo de segurança da plataforma cria armadilhas que só aparecem quando você usa um Site Guest User de verdade (e não os mocks dos testes).
Armadilha 1: o Guest User não pode editar objeto
O webhook público chega no contexto do Site Guest User (usuário não autenticado). Desde o
Winter '21, a licença de guest não permite conceder Edit em objeto customizado.
Se o seu Permission Set tenta dar allowEdit=true, a atribuição falha:
FIELD_INTEGRITY_EXCEPTION: A licença do usuário não permite
a autorização: Editar Contract_Envelope__c
Ou seja: o guest nunca vai conseguir atualizar o registro de rastreamento como usuário. A solução correta é gravar em modo sistema, tratando a validação HMAC como a real fronteira de segurança (mais sobre isso na armadilha 3):
// O chamador é o Guest User; a segurança é o HMAC validado antes daqui.
update envelope; // modo sistema, não `update as user`
Armadilha 2: o Guest User não enxerga registros de terceiros
Mesmo dando permissão de leitura, o secure guest user record access só deixa o guest ver os
registros que ele mesmo possui. Como os envelopes pertencem ao usuário interno que enviou o
contrato, um SELECT ... WITH USER_MODE rodando no webhook volta vazio — e o
processor conclui, errado, que "o envelope não existe".
A correção é fazer a busca do webhook rodar sem sharing e em modo sistema, sem afrouxar o fluxo de usuário.
O padrão elegante é inherited sharing no selector: ele adota o contexto de quem chama —
with sharing no controller do usuário, without sharing no processor do webhook.
public inherited sharing class ContractEnvelopeSelector {
public static Contract_Envelope__c getByEnvelopeKey(String key) {
return [SELECT Id, Status__c FROM Contract_Envelope__c
WHERE Envelope_Key__c = :key WITH SYSTEM_MODE LIMIT 1];
}
}
Armadilha 3: validação HMAC, base64, hex e o header que muda
O que autoriza o webhook não é o usuário, é a assinatura HMAC-SHA256: o provedor assina o corpo com um segredo compartilhado, e você recalcula e compara antes de qualquer gravação. Dois detalhes derrubam integrações aqui:
- base64 vs hex: provedores diferentes codificam a assinatura de formas diferentes. Compare contra as duas representações.
- o nome do header varia (
Content-Hmac,X-Hub-Signature...) e a doc pública nem sempre confirma. Deixe o nome do header configurável via Custom Metadata. Sintoma clássico de header errado: todos os webhooks chegam e retornam 401.
Blob mac = Crypto.generateMac('HmacSHA256',
Blob.valueOf(payload), Blob.valueOf(secret));
Boolean ok = recebida == EncodingUtil.base64Encode(mac)
|| recebida.equalsIgnoreCase(EncodingUtil.convertToHex(mac));
Armadilha 4: idempotência, quando o mesmo evento chega duas vezes
Plataformas de assinatura reenviam webhooks e entregam eventos fora de ordem.
Sem proteção, um evento cancel atrasado que chega depois de um sign regride um
contrato já assinado. A defesa é uma guarda de estado terminal: se o registro já está em estado final,
ignore o evento e responda 200 sem gravar nada.
if (TERMINAL_STATUSES.contains(envelope.Status__c)) {
return; // já assinado/recusado/cancelado: nada regride
}
Bônus: a ordem do setup manual importa
Uma etapa não é código, mas quebra a integração se feita na ordem errada: salve o segredo HMAC no Salesforce antes de ativar o webhook no provedor. Se o webhook começar a mandar eventos antes do segredo estar salvo, tudo é rejeitado com 401 e parece "bug" quando é só sequência.
Como validar sem depender de uma assinatura real
Um teste rápido prova que o endpoint está público e o portão de segurança funciona: mande um POST sem assinatura válida e confirme o 401.
curl -X POST https://SEU-SITE/services/apexrest/clicksign-webhook \
-H "Content-Hmac: assinatura-invalida" -d '{"event":{"name":"sign"}}'
# esperado: HTTP 401 {"error":"invalid_signature"}
Atenção: os testes de unidade costumam usar mocks que mascaram as armadilhas 1 e 2 — elas só
aparecem com um Site real. Por isso o teste do curl e um health check da configuração são
indispensáveis, além dos testes Apex.
Precisa integrar assinatura eletrônica (ou outro sistema) na sua org?
Eu faço a integração completa: geração de PDF, envio, webhook seguro e idempotente, testado e documentado. Comece com um diagnóstico gratuito de 45 minutos.
Falar no WhatsApp Ver serviços