Pular para o conteúdo
TCloud desenvolvedores
TCloud Assinador

Guia de integração para desenvolvedores

Guia direto ao ponto para ligar um sistema (Atlas, BookC ou qualquer outro) ao TCloud Assinador. Trata só da integração: arquitetura, exemplos, protocolo, estados, erros e homologação. Instalação nas estações e uso do aplicativo estão no manual geral (INTEGRACAO.md).

versão 2.6.1 atualizado em 23/09/2026 17 min de leitura
Começar a leitura Baixar .html

1O essencial em dois minutos

Não há serviço de assinatura na VM. O seu sistema guarda o pedido e o TCloud Assinador instalado no computador do usuário faz todo o trabalho: baixa os documentos, assina o arquivo inteiro ali mesmo e devolve os assinados ao seu sistema.

  1. 1
    NavegadorUsuário clica em Assinar
  2. 2
    VMO PHP cria o pedido e o ticketTCloudAssinadorLocal
  3. 3
    Computador do usuárioO link tcloudsign://local abre o TCloud Assinador, que baixa os documentos
  4. 4
    Computador do usuárioEscolhe o certificado, digita o PIN e assina o arquivo inteiro
  5. 5
    VMRecebe os assinados e grava no sistema
  6. 6
    NavegadorMostra o resultado
Quem assina é a versão instalada no computador do usuário. A VM só guarda o pedido por alguns minutos.

O que o seu sistema faz:

  1. autenticar o usuário e decidir se ele pode assinar aquele documento (o assinador não faz isso);
  2. criar o pedido com os documentos (iniciar) e devolver o link ao navegador;
  3. atender o app da estação pelas ações ticket_* (a classe PHP faz tudo; você só encaminha);
  4. acompanhar o estado e, ao concluir, gravar os arquivos assinados.

O que o assinador faz, no computador do usuário: mostra a janela de confirmação com os documentos e os certificados, pede o PIN do token, assina (PDF com chancela e bloqueio contra alterações, XML ou JSON), aplica o carimbo de tempo se a estação tiver ACT, e devolve os arquivos.

Quem assina é a versão instalada na estação. Atualizar o assinador é atualizar as estações; na VM não há nada do assinador instalado. Ele existe para Windows, Mac e Linux, com a mesma interface e o mesmo protocolo: o seu sistema não precisa saber em qual deles o usuário está.

Peças do lado do sistema (pasta atlas\ do pacote):

Arquivo Papel
TCloudAssinadorLocal.php A classe: cria pedidos e tickets, guarda os documentos, atende a estação, entrega os assinados.
TCloudAssinador.php Traz a TCloudAssinadorException, usada pela classe acima.
assinador-endpoint.php Endpoint de exemplo, pronto para adaptar (três AJUSTES).
tcloud-assinar.js A tela: abre o link, acompanha o pedido e mostra as mensagens.

Requisitos: PHP 5.6 ou mais novo, sem extensões especiais; a estação precisa alcançar o endereço do endpoint (é o mesmo que o navegador já usa).

2Integração mínima, de ponta a ponta

2.1Endpoint do sistema

O assinador-endpoint.php do pacote já é a integração completa; basta fazer os três AJUSTES. A estrutura dele:

PHP
<?php
require_once __DIR__ . '/TCloudAssinador.php';
require_once __DIR__ . '/TCloudAssinadorLocal.php';

$CONFIG_LOCAL = [
    'sistema' => 'Atlas',
    // 'url_endpoint' => 'https://atlas.cartorio/signum/assinador-endpoint.php', // se houver proxy
    // 'exigir_mesmo_ip' => false,                                              // idem
];

$acao = isset($_REQUEST['acao']) ? $_REQUEST['acao'] : '';

// 1) O app da estação fala pelo ticket, SEM sessão: atende ANTES do login.
if (strpos($acao, 'ticket_') === 0) {
    (new TCloudAssinadorLocal($CONFIG_LOCAL))->atenderEstacao($acao);
    exit;
}

// 2) Daqui para baixo, só o navegador do usuário logado.
session_start();
header('Content-Type: application/json; charset=utf-8');

// AJUSTE 1 — a sua autenticação.
$usuario = isset($_SESSION['usuario_login']) ? $_SESSION['usuario_login'] : null;
$nome    = isset($_SESSION['usuario_nome']) ? $_SESSION['usuario_nome'] : $usuario;
if (!$usuario) responder(['ok' => false, 'erro' => 'Sessão expirada.'], 401);

// AJUSTE 2 — onde está o documento de cada id (e se o usuário pode assiná-lo).
function caminho_do_documento($id) { /* ... */ }

// AJUSTE 3 — o que fazer com cada documento assinado (PDF, XML ou JSON).
function gravar_assinado($id, $caminhoOriginal, $conteudo, $nomeAssinado = null) { /* ... */ }

$assinador = new TCloudAssinadorLocal($CONFIG_LOCAL);

switch ($acao) {
    case 'iniciar':                                   // POST documentos[]=id&titulo=...
        $ids = isset($_POST['documentos']) ? (array)$_POST['documentos'] : [];
        $docs = []; $mapa = [];
        foreach ($ids as $id) {
            $caminho = caminho_do_documento($id);
            if (!$caminho) responder(['ok' => false, 'erro' => "Documento $id não encontrado."], 404);
            $docs[] = ['nome' => basename($caminho), 'arquivo' => $caminho];
            $mapa[] = ['id' => $id, 'caminho' => $caminho];
        }
        $r = $assinador->iniciar($usuario, $docs, [
            'nome' => $nome,
            'titulo' => isset($_POST['titulo']) ? $_POST['titulo'] : null,
            'motivo' => 'Assinatura digital',
            'aparencia' => ['visivel' => true, 'pagina' => -1, 'x' => 36, 'y' => 36,
                            'largura' => 220, 'altura' => 80, 'ancorar_direita' => true],
        ]);
        $_SESSION['tcloud_pedidos'][$r['id']] = $mapa;   // o pedido pertence a esta sessão
        responder($r);                                   // {ok, id, estado, modo, link}

    case 'status':                                    // GET id=...
        $id = isset($_GET['id']) ? $_GET['id'] : '';
        if (!isset($_SESSION['tcloud_pedidos'][$id])) responder(['ok' => false, 'erro' => 'Pedido desconhecido.'], 404);
        $s = $assinador->status($id);
        if ($s['estado'] === 'concluido') {
            // Concluiu: grava aqui mesmo. O JavaScript só acompanha o estado.
            $res = $assinador->resultado($id);
            foreach ($res['documentos'] as $i => $d) {
                if (!empty($d['pdf'])) {                 // 'pdf' traz os bytes (de qualquer formato)
                    $m = $_SESSION['tcloud_pedidos'][$id][$i];
                    gravar_assinado($m['id'], $m['caminho'], $d['pdf'], $d['nome_assinado']);
                }
            }
            unset($_SESSION['tcloud_pedidos'][$id]);
        }
        responder($s);

    case 'cancelar':                                  // POST id=...
        responder($assinador->cancelar(isset($_POST['id']) ? $_POST['id'] : ''));
}

Pontos que o exemplo real trata e que valem para qualquer adaptação:

  • As ações ticket_* vêm antes da sessão. O app da estação não tem o cookie do navegador; a credencial dele é o ticket.
  • O pedido fica preso à sessão ($_SESSION['tcloud_pedidos']): um usuário não consulta nem cancela o pedido de outro.
  • O navegador manda formulário, não JSON: documentos[] e titulo por POST em iniciar, id por GET em status e por POST em cancelar. É o formato que o tcloud-assinar.js usa.
  • Grave na ação status, quando o estado vira concluido. O exemplo real também guarda o resultado numa segunda chave de sessão, para que uma consulta repetida não grave duas vezes.

2.2Tela

HTML
<script src="tcloud-assinar.js"></script>
<script>
const assinador = new TCloudAssinar({
  endpoint: 'assinador-endpoint.php',
  cabecalhos: { 'X-CSRF-Token': tokenCsrf },        // opcional
  urlInstalacao: 'https://tcloudsoft.app/download'  // opcional: "Não abriu? Instale…"
});

document.getElementById('btn-assinar').onclick = async () => {
  try {
    const s = await assinador.assinar([12, 13], { titulo: 'Protocolo 5521' });
    console.log(s.mensagem);          // resolve com o status do pedido concluído
    location.reload();
  } catch (e) {
    if (e.codigo === 'cancelado') return;                   // o usuário fechou a janela
    alert(e.message + ' (' + (e.codigo || 'erro') + ')');   // recusado, expirado, erro…
  }
};
</script>

O tcloud-assinar.js chama iniciar, abre o link tcloudsign://local, mostra o botão Abrir o TCloud Assinador se o navegador não abrir sozinho, consulta o estado e mostra as mensagens. Nenhuma dependência. O JavaScript nunca vê o conteúdo dos documentos.

Opção Padrão Para quê
endpoint 'assinador-endpoint.php' Endereço do seu endpoint.
intervalo 1500 Intervalo do polling, em milissegundos.
cabecalhos {} Cabeçalhos extras (CSRF, por exemplo).
urlInstalacao — Link de instalação mostrado quando o assinador não abre.

assinar(documentos, extra) devolve uma Promise: resolve com o status final e rejeita com um Error que traz codigo (cancelado, recusado, expirado, erro…).

2.3A classe TCloudAssinadorLocal

PHP
$assinador = new TCloudAssinadorLocal([
    'sistema'          => 'Atlas',  // nome na janela ("Atlas pede a sua assinatura")
    'url_endpoint'     => null,     // endereço deste endpoint visto pela estação (padrão: o da requisição)
    'pasta'            => null,     // onde guardar os pedidos (padrão: pasta temporária do sistema)
    'minutos_abrir'    => 3,        // prazo para a estação abrir o link
    'minutos_concluir' => 15,       // prazo para concluir depois de aberto
    'exigir_mesmo_ip'  => true,     // o link só abre no IP de quem clicou
]);
Método Devolve Para quê
iniciar($usuario, $documentos, $opcoes) ['ok', 'id', 'estado', 'modo' => 'local', 'link'] Cria o pedido. Cada documento leva arquivo (caminho) ou pdf (bytes — o nome do campo é pdf para qualquer formato), e nome opcional.
status($id) ['ok', 'id', 'estado', 'finalizado', 'mensagem', 'estacao', 'documentos'] Estado do pedido; documentos traz nome, assinado e, se houver, erro.
resultado($id) ['ok', 'id', 'documentos'] Só com o pedido concluido (senão, exceção com status 409). Cada documento traz pdf em bytes, nome_assinado, assinado, erro e avisos.
cancelar($id) ['ok'] Encerra o pedido.
atenderEstacao($acao) — (imprime o JSON) Atende as ações ticket_* do app da estação.
acaoEstacao($acao, $corpo) array O mesmo, sem HTTP (útil em testes e frameworks).

Erros viram TCloudAssinadorException, com ->codigo e ->status (HTTP): sem_documentos, arquivo, documento, pasta, pedido (404), ticket (403/404), outro_computador (403), acao (404), ou o nome do estado quando o pedido já foi encerrado.

Os pedidos ficam em arquivos na pasta temporária do servidor (sem banco de dados) e são apagados 30 minutos depois de encerrados, ou 1 hora depois de criados se ninguém os concluir.

3O que acontece em cada etapa

3.1Criar o pedido (iniciar)

O PHP guarda os documentos, gera um ticket de 256 bits e devolve:

JSON
{ "ok": true, "id": "LDTJw8VJ_gx3h", "estado": "aguardando_estacao", "modo": "local",
  "link": "tcloudsign://local?u=https%3A%2F%2Fatlas.cartorio%2Fsignum%2Fassinador-endpoint.php&t=Qm9…" }

O link leva o endereço do seu endpoint (u) e o ticket (t). Ele precisa ser aberto no navegador do computador de quem clicou — o tcloud-assinar.js faz isso por um <iframe> escondido e oferece também um botão, porque um clique de verdade é o que os navegadores mais aceitam para abrir outro programa.

O sistema operacional entrega o link ao TCloud Assinador (o instalador registrou o tcloudsign:// no Windows, no Mac e no Linux). Na primeira vez que um sistema aparece, o assinador pergunta "Confiar neste servidor?" e lembra a resposta. Então ele chama as ações ticket_* do seu endpoint (seção 4): abre o pedido, baixa os documentos, mostra a janela de confirmação, assina e devolve cada arquivo.

3.3Acompanhar e gravar

O tcloud-assinar.js consulta status a cada 1,5 s. Quando o estado vira concluido, o seu endpoint chama resultado() e grava os arquivos. Um pedido pode concluir parcialmente: percorra documentos olhando assinado e erro de cada um.

4Protocolo entre a estação e o seu endpoint (ticket_*)

Só interessa a quem não usa a classe TCloudAssinadorLocal (outra linguagem, outro framework). Todas as ações são POST <seu endpoint>?acao=<ação>, com corpo JSON contendo o ticket, e respondem JSON.

Ação Corpo além do ticket Resposta
ticket_abrir — {ok, pedido: {id, sistema, titulo, usuario, nome, documentos: [nomes], opcoes: {…}, minutos}}
ticket_documento indice {ok, nome, conteudo_base64}
ticket_entregar indice, nome_assinado, conteudo_base64, avisos — ou indice, erro {ok}
ticket_concluir — {ok, mensagem}
ticket_recusar motivo {ok}

Formato do link: tcloudsign://local?u=<URL do endpoint, codificada>&t=<ticket>. O u precisa ser http ou https; o ticket, base64url com pelo menos 20 caracteres.

Regras que a implementação precisa garantir (a classe PHP já faz):

  • ticket aleatório de pelo menos 128 bits (a classe usa 256), guardado só como hash;
  • ticket_abrir só aceita o IP de quem criou o pedido (configurável), grava esse IP, e as demais ações só aceitam esse IP;
  • depois de encerrado, o pedido não reabre: o ticket é de uso único na prática;
  • prazos: 3 minutos para abrir, 15 para concluir; depois, expirado;
  • erros em JSON {ok: false, erro, codigo} com status HTTP 4xx — o app mostra o erro ao usuário.

opcoes leva as opções do pedido da seção 5, que a estação aplica na assinatura.

5Opções do pedido

As opções vão no terceiro argumento de iniciar() e chegam à estação em opcoes.

5.1Gerais

Opção Padrão Descrição
nome o usuario Nome de quem pediu, mostrado na janela ("Solicitado por …").
titulo — Texto de contexto na janela (ex.: protocolo). Não vai para o documento.
sistema o da configuração Nome do sistema na janela.
motivo o da estação Motivo gravado na assinatura e mostrado na chancela.
local o da estação Local gravado na assinatura.
nivel o da estação basico (sem carimbo), carimbo ou ltv. A ACT é sempre a da estação, configurada no app: sem ACT, o documento sai sem carimbo e com um aviso. No Windows, ltv acrescenta a validação de longo prazo; no Mac e no Linux, vale como carimbo.
bloqueio assinaturas Bloqueio contra alterações aplicado pela primeira assinatura de um PDF: assinaturas (bloqueia edição e permite novas assinaturas), total ou nenhum.

5.2XML e JSON

O formato é detectado pelo conteúdo de cada documento (a extensão é o desempate); um mesmo pedido pode misturar PDF, XML e JSON.

Formato Padrão Detalhes
PDF PAdES Chancela, bloqueio contra alterações e carimbo.
XML XMLDSig envelopada + XAdES básico, RSA-SHA256, canonicalização exclusiva A assinatura entra como último filho da raiz; o resto do arquivo não muda. Com carimbo, vira XAdES-T.
JSON JWS (RFC 7515) achatado, com x5c, x5t#S256 e sigT (JAdES) Embutida (padrão) ou destacada. Com carimbo, o token da ACT vai em header.etsiU[].sigTst.
Opção Padrão Descrição
xml_multiplas false Assinar XML que já tem assinatura. Numa envelopada sobre o documento inteiro, a nova invalida a anterior (é do formato — por isso a NF-e admite só uma).
json_destacado false Assinatura em arquivo .jws ao lado, com o JSON original intacto.

Nomes dos arquivos devolvidos (nome_assinado): nome-assinado.pdf, nome-assinado.xml, nome-assinado.json e, na forma destacada, nome.jws.

5.3Chancela (aparencia, só PDF)

Sem aparencia, a assinatura é invisível (vale juridicamente, mas não marca a página).

Opção Padrão Descrição
visivel true Desenha a chancela.
pagina 1 1 = primeira, -1 = última, N = página N.
x / y 36 Distância em pontos até a borda de referência.
largura / altura 200 / 70 Tamanho em pontos.
ancorar_direita / ancorar_topo false Mede x pela direita e y pelo topo.
titulo — Linha em negrito no topo (aceita \n).
texto_adicional — Linhas extras depois dos dados.
texto — Substitui todas as linhas automáticas.
logo_base64 — Logomarca ou brasão (PNG recomendado; JPG e outros são convertidos na estação).
logo_posicao esquerda esquerda, acima ou fundo (marca d'água).
borda true Borda em volta da chancela.

Unidades: pontos. 1 cm ≈ 28,35 pt; A4 = 595 × 842 pt. A posição é medida a partir do canto escolhido, então a chancela fica no mesmo lugar em páginas de tamanhos diferentes.

6Estados e tempos

Estado Significa finalizado
aguardando_estacao Criado; esperando o TCloud Assinador abrir o link. não
na_estacao A janela de confirmação está aberta no computador do usuário. não
assinando A estação já baixou os documentos e está assinando. não
concluido Pelo menos um documento foi assinado. Chame resultado() e grave. sim
recusado O usuário recusou, fechou a janela ou cancelou o PIN. sim
expirado A estação não abriu o link a tempo (o assinador está instalado?) ou não concluiu no prazo. sim
erro Nenhum documento pôde ser assinado. Veja mensagem. sim
cancelado O sistema cancelou. sim

Tempos padrão: 3 minutos para a estação abrir o link (minutos_abrir), 15 para concluir depois de aberto (minutos_concluir). O pedido é apagado 30 minutos depois de encerrado; daí em diante, consultar dá erro pedido (404).

Repetição. Criar pedido não é idempotente: cada chamada cria um pedido e um link novos. Consultar o estado e ler o resultado podem ser repetidos à vontade enquanto o pedido existir. Um pedido por clique; ao trocar de tela ou fechar o modal, chame cancelar.

7Boas práticas

  • Permissão é sua. O assinador assina o que o seu sistema mandou. Confira no caminho_do_documento se aquele usuário pode assinar aquele documento.
  • Prenda o pedido à sessão, como o exemplo faz, para ninguém consultar ou cancelar o pedido de outro.
  • Atrás de proxy ou balanceador, informe url_endpoint (o endereço que a estação deve usar) e, se o IP real de quem clicou não chegar ao PHP, 'exigir_mesmo_ip' => false.
  • Documentos grandes: o assinado volta num POST com o arquivo em base64, cerca de um terço maior que o original. Ajuste o post_max_size do PHP de acordo.
  • Mostre a mensagem do estado ao usuário: ela já vem escrita para ele.
  • Trate avisos: um documento sem carimbo porque a estação não tinha ACT continua válido; decida se isso basta para o seu fluxo.
  • Nunca registre em log o ticket nem o conteúdo dos documentos.

8Segurança

Camada O que protege
Sessão do seu sistema Só o usuário logado cria, consulta e cancela os próprios pedidos.
Ticket 256 bits aleatórios, guardado só como hash, de uso único, com prazo curto.
IP de quem clicou Por padrão, o link só abre no computador de quem clicou, e depois de aberto fica preso a ele.
"Confiar neste servidor?" Um link tcloudsign:// pode ser aberto por qualquer site; o assinador confirma o sistema na primeira vez e lembra a resposta.
Chave privada Nunca sai do token (A3) nem do computador do usuário (A1).
Documentos no servidor Na pasta temporária, fora da pasta pública do site, apagados depois do pedido.
Bloqueio do PDF A primeira assinatura certifica o documento; qualquer edição posterior aparece como violação na validação.

No endpoint, aplique o de sempre às ações do navegador: sessão autenticada, CSRF e limitação de taxa. As ações ticket_* não usam sessão, e por isso a proteção delas é o ticket e o IP.

9Homologação

Dá para testar tudo num computador só: rode o Atlas/BookC nele (XAMPP, por exemplo), instale o TCloud Assinador e assine pelo sistema em http://localhost/....

10Erros comuns

Sintoma Causa O que fazer
Fica em "Abrindo o TCloud Assinador…" e expira O assinador não está instalado nesse computador, ou o navegador bloqueou o link. Instale-o; ofereça o botão "Abrir o TCloud Assinador" e permita abrir o programa no navegador.
"Este link foi gerado para outro computador" O IP que chega ao PHP não é o do computador (proxy no caminho). Informe url_endpoint e use 'exigir_mesmo_ip' => false.
O assinador não consegue falar com o sistema A estação não alcança o url_endpoint (proxy, DNS, firewall). Abra o endereço do endpoint no navegador da estação; ajuste url_endpoint.
"Este link de assinatura não vale mais" Pedido encerrado, expirado ou já apagado. Clicar em "Assinar" de novo cria um pedido novo.
O assinador mostra "Sessão expirada" (ou a mensagem de login do seu sistema) ao abrir o pedido As ações ticket_* estão passando pela verificação de login. Encaminhe-as à classe antes do session_start e da autenticação.
O documento grande não volta O POST com o assinado passou do post_max_size. Aumente o post_max_size (e o upload_max_filesize, se o servidor exigir).
Documento sem carimbo, com aviso A estação não tem ACT configurada. Configure a ACT nas Configurações do app, na estação.
pasta ao criar a classe O PHP não consegue escrever na pasta temporária. Informe pasta com uma pasta gravável, fora da pasta pública do site.

11Compatibilidade e histórico

O contrato só cresce: campos novos entram sem quebrar quem já integrou. Faça o seu cliente ignorar campos desconhecidos.

Versão Mudança
2.6.0 Versão para Linux, com a mesma interface e o mesmo protocolo. Nada muda no seu sistema.
2.5.0 O instalador passou a instalar só o app da estação: não instala mais o servidor de assinatura da VM (e remove o das versões anteriores).
2.4.0 Modo local (tcloudsign://local): classe TCloudAssinadorLocal, ações ticket_* no endpoint, assinatura inteira na estação.
2.2.0 Pedidos aceitam XML e JSON além de PDF, detectados pelo conteúdo; resultado com nome_assinado.
2.0.0 Opção bloqueio; a primeira assinatura passou a certificar o PDF contra alterações.

Integrações antigas, com o serviço da VM (classe TCloudAssinador, porta 9480, X-TCloud-Token, rotas /sistema/*): continuam funcionando onde o serviço ainda estiver instalado, mas ele não é mais distribuído. Para migrar, troque new TCloudAssinador(...) por new TCloudAssinadorLocal(...) — os métodos iniciar, status, resultado e cancelar têm os mesmos nomes e o mesmo formato — e encaminhe as ações ticket_* antes do login, como na seção 2.1. O tcloud-assinar.js não muda. O código do serviço continua no projeto (src\TCloud.PdfSigner.Agent) e o manual geral descreve esse modo na seção 2.7.

Downloads