Integração Salesforce

Integrar Salesforce com ClickSign: as 4 armadilhas do webhook e do Guest User

Ilustração do artigo

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