WP Manifestindependent plugin directory
manifest / integrations / wordpress-plugin

Assinafy

Plugin oficial do WordPress para a Assinafy: assinatura eletrônica e digital com ICP-Brasil, conforme a MP 2.200-2. Official WordPress plugin for Brazilian e-signature.

by Assinafy · github.com/assinafy/wordpress-plugin · website

0stars
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/assinafy/wordpress-plugin/releases/download/v1.0.0/assinafy-1.0.0.zip

Assinafy para WordPress

Português · Read in English

Envie um PDF do WordPress para assinatura eletrônica com a Assinafy e acompanhe-o do upload ao artefato assinado sem sair do admin.

Este documento descreve o código-fonte atual do plugin e seu contrato de integração. Os payloads da API são exemplos ilustrativos, com identidades e credenciais fictícias; saldos, preços, tempos e recursos disponíveis na conta devem ser lidos da conta configurada.


Os resultados detalhados da verificação, as correções e os limites de cobertura estão em AUDIT.md no checkout do código-fonte. Esse relatório interno de auditoria é excluído dos ZIPs de release. O contrato entre core e adaptadores e o rollout das integrações estão em docs/integrations.md.

1. O que este plugin faz

Ele envia um PDF para uma conta Assinafy, pede que pessoas nomeadas o assinem, espelha o estado remoto em um custom post type do WordPress e entrega os artefatos finalizados de volta por um proxy protegido por capability. Todo o resto do plugin — a tela de configurações, o receptor de webhook, o cron de reconciliação, o WP-CLI, o WooCommerce — existe para tornar esse único caminho confiável.

O core é dono do envio, da recuperação, do armazenamento, dos webhooks e da sincronização. Comportamento específico de cada host pertence aos adaptadores. WooCommerce e Elementor Forms são entregues como adaptadores embutidos. Gravity Forms, Contact Form 7 e WPForms têm add-ons de desenvolvimento separados no checkout do código-fonte, em addons/, na versão 0.1.0. Cada um envia um PDF existente com nome/e-mail do signatário mapeados através do core. CF7 e WPForms Lite são testados contra plugins host gratuitos reais; Gravity Forms e Elementor Pro têm apenas testes documentados de contrato de API, pendentes de validação com instalação licenciada. A vinculação de entradas do WPForms Pro também não foi verificada em um host licenciado. Um produto WooCommerce separado só vem depois que seu workflow de contrato estiver definido. Veja o guia de adaptadores e a configuração do Elementor.

Adaptadores de formulário

Adaptador Instalação Disparo e recuperação Limite de verificação
Gravity Forms Add-on separado assinafy-gravity-forms Feed nativo em segundo plano; regras condicionais; identidade única por entrada/feed; retentativa na página da entrada Exige Gravity Forms 2.9.4+; apenas dublês de host documentados
Contact Form 7 Add-on separado assinafy-contact-form-7 Resultado de e-mail aceito com sucesso e consentimento configurado; recibo do core e retentativa protegida CF7 6.1.7 real
WPForms Add-on separado assinafy-wpforms Processamento bem-sucedido; recibo do core mesmo sem armazenamento de entradas no Lite; retentativa protegida WPForms Lite 2.0.1.1 real; Pro não verificado em execução
Elementor Forms Incluído no core; adicione a ação Assinafy Ação de formulário nativa, deduplicação de callback no mesmo registro; detalhes de erro de recuperação do core Exige Elementor Pro; apenas dublês de host documentados; sem UI de retentativa no formulário

Estes são fluxos iniciais de envio de PDF. Eles não geram contratos, não mesclam valores do formulário dentro de PDFs, não exigem pagamento e não condicionam a entrega a uma assinatura. Configure o consentimento no host. CF7 e WPForms guardam a configuração mínima de retentativa no documento do core, não a submissão completa. Submissões distintas aceitas sem entrada armazenada são requisições distintas, mesmo com valores idênticos; retentativas de callback reutilizam o recibo. A eliminação por privacidade bloqueia o reenvio a contatos apagados. A tela de documento do core continua sendo o lugar compartilhado para status, links de assinatura e downloads.

Construa o core com bin/build-zip.sh e depois rode bin/build-addons.sh para criar os três ZIPs separados em dist/addons/. O ZIP do core exclui addons/; instale apenas o add-on do plugin de formulário que você usa. Cada diretório de add-on inclui seu próprio README de configuração e um readme.txt. Esses artefatos são builds de desenvolvimento, não releases publicados no WordPress.org. O Plugin Check oficial não reporta erros; os add-ons ainda têm avisos de revisão de diretório porque o core exigido não está listado, e o nome/slug do WPForms é sinalizado. Esses avisos e a validação em host licenciado continuam sendo travas de release.

O modelo de domínio

A Assinafy não tem envelope. O grafo de objetos é plano, e entender seus cinco substantivos é quase tudo de que você precisa:

Entidade Escopo Identidade O que é
Account workspace {ACCOUNT_ID} Coleções da conta usam accounts/{accountId}/…; operações em documentos individuais usam documents/{documentId}/….
Document conta string hexadecimal opaca O PDF e seu ciclo de vida. Carrega artifacts, pages[], tags[] e um assignment embutido.
Signer conta string hexadecimal opaca Um registro de pessoa reutilizável. email é único por conta.
Assignment documento, 1:1, permanente string hexadecimal opaca A própria solicitação de assinatura. Um por documento, para sempre. Não existe rota de atualização.
Artifact documento um nome, não um id Um arquivo baixável derivado do documento: original, certificated, certificate-page, pades, bundle.

Os ids são strings hexadecimais opacas de comprimento variável — 26 a 28 caracteres observados em uma conta. Guarde-os como strings. Nunca valide com uma regex de comprimento fixo.

Três consequências saem direto do modelo e moldam o plugin inteiro:

  1. Um assignment não pode ser editado. Trocar um signatário, uma mensagem ou um método significa subir o documento de novo. O plugin oferece reenviar, estender e cancelar — as três operações pós-envio que a API realmente tem — e nada que finja ser uma edição.
  2. Um signatário é identificado pelo e-mail, em toda a conta. Enviar para a mesma pessoa duas vezes exige buscar o signatário antes de criar um, ou o segundo envio falha.
  3. Os endpoints de artefato exigem autenticação da conta. O plugin guarda os nomes dos artefatos e usa o DownloadProxy para buscar seus bytes no servidor com a chave de API. Os links de download do navegador apontam para o proxy do WordPress, que verifica permissões.

A máquina de estados

Um documento passa por exatamente onze status. is_closed: true marca todos os terminais — responda "isto acabou?" por essa flag, não por uma lista mantida à mão.

stateDiagram-v2
    [*] --> uploading: POST /accounts/{acc}/documents
    uploading --> uploaded
    uploaded --> metadata_processing
    metadata_processing --> metadata_ready

    uploaded --> failed: bad content
    metadata_processing --> failed: processing error

    uploaded --> pending_signature: virtual assignment<br/>(promoted automatically)
    metadata_ready --> pending_signature: collect assignment

    pending_signature --> certificating: last signer signs
    certificating --> certificated

    pending_signature --> rejected_by_signer: a signer declines
    pending_signature --> rejected_by_user: an account user cancels
    pending_signature --> expired: expires_at passes

    certificated --> [*]
    rejected_by_signer --> [*]
    rejected_by_user --> [*]
    expired --> [*]
    failed --> [*]
Código Excluível Significado
uploading não Transitório, raramente observado.
uploaded não Bytes aceitos. pages: []. Um assignment virtual pode já existir.
metadata_processing não Páginas sendo renderizadas. pages[] é preenchido durante este estado.
metadata_ready sim Páginas renderizadas, miniatura disponível.
pending_signature sim Signatários notificados. Excluir é o único cancelamento que a API oferece.
certificating não O último signatário assinou; a plataforma está selando o PDF.
certificated não Sucesso terminal; marcado como não excluível no catálogo de status atual.
rejected_by_signer sim Um signatário recusou. Terminal.
rejected_by_user sim Um usuário da conta cancelou. Terminal.
expired sim expires_at passou. Terminal.
failed sim Conteúdo rejeitado ou falha no processamento. Terminal.

Não existe status ready. document_ready — o evento de webhook — significa "o último signatário assinou", não um status com esse nome.

O plugin cria um assignment virtual depois do upload, sem fazer polling por metadata_ready. As respostas seguintes da API e a reconciliação fornecem o status de processamento/assinatura do documento.

Métodos de verificação e de notificação

O plugin suporta três métodos de verificação e um canal de notificação por signatário. Os valores são PascalCase; combinações incompatíveis de método/canal são rejeitadas localmente.

verification_method notification_methods Custo Exige
Email ["Email"] Estimativa da conta E-mail, ou um ID de signatário existente
Whatsapp ["Whatsapp"] Estimativa da conta; restrições de plano podem se aplicar Número de telefone, ou um ID de signatário existente
DigitalCertificate ["Email"] ou ["Whatsapp"] Estimativa da conta; o recurso precisa estar habilitado ID de signatário existente com documento de identificação configurado na Assinafy; signatário sozinho em sua etapa

Signers valida o pareamento. Omitir o método e o canal usa Email como padrão quando há e-mail ou quando nenhum telefone é informado, e Whatsapp caso contrário. Canais explícitos suportados são preservados.

Esses três são todo o vocabulário de verificação. O plugin não oferece nenhum método que a API não implemente.


2. Requisitos e instalação

Mínimo Por quê
PHP 8.2 O assinafy/php-sdk exige ^8.2. O bootstrap mostra um aviso no admin e retorna em qualquer versão anterior, em vez de causar um erro fatal.
WordPress 6.8 A release que estendeu o carregamento just-in-time de traduções a todos os plugins. As traduções são distribuídas como language packs do wordpress.org e carregam a partir de WP_LANG_DIR sem o plugin pedir, portanto não há chamada a load_plugin_textdomain() nem catálogo compilado no pacote.
Testado até 7.1
Extensões sodium, mbstring Criptografia das credenciais e tratamento de Unicode pelo SDK; JSON já vem embutido nas versões de PHP suportadas.
WooCommerce (opcional) 10.2.2 Testado com WooCommerce 10.2.2 e 11.1.0; o core também inicia sem o WooCommerce.

WooCommerce e WP-CLI são opcionais; cada integração carrega apenas quando seu host está presente.

Instalando uma release

Baixe assinafy-<version>.zip na página de releases e instale por Plugins → Adicionar novo → Enviar plugin. O zip já traz a árvore de dependências com prefixo; nada precisa ser compilado no servidor.

Instalando a partir do código-fonte

# From a checkout or extracted source tree named assinafy:
cd assinafy
composer install

O composer install roda o Strauss no post-install-cmd, que copia a árvore de dependências para vendor-prefixed/ e reescreve Psr\Log\ como Assinafy\WP\Vendor\Psr\Log\. O plugin carrega vendor-prefixed/autoload.php e nunca vendor/autoload.php — é ali que estão as classes distribuídas.

O SDK em si mantém o namespace Assinafy\SDK\. A coexistência com outro plugin que carregue uma versão incompatível do SDK não foi verificada; apenas o logger PSR embutido recebe prefixo sob Assinafy\WP\Vendor\.

Sem Guzzle, por design

O composer.json declara replace para toda a árvore do Guzzle, então ela nunca é instalada:

"replace": {
    "guzzlehttp/guzzle": "*", "guzzlehttp/promises": "*", "guzzlehttp/psr7": "*",
    "psr/http-client": "*", "psr/http-factory": "*", "psr/http-message": "*",
    "symfony/polyfill-php80": "*", "symfony/polyfill-php82": "*"
}

O AssinafyClient do SDK aceita um transporte injetado, e o plugin fornece o WpHttpClient, construído sobre wp_remote_request(). Isso elimina um erro fatal em todo o site: GuzzleHttp\Client é exatamente o mesmo nome de classe totalmente qualificado no Guzzle 6, 7 e 8, então dois plugins embutindo majors diferentes colidem no nível da classe e derrubam o site inteiro. Não distribuir Guzzle nenhum apaga esse modo de falha, e o job de CI runtime-smoke verifica class_exists( 'GuzzleHttp\Client' ) === false na árvore de produção em todo pipeline.

Isso também significa que o plugin nunca deve chamar AssinafyClient::create(), ::fromArray(), ::forAuth() ou ::forBearer() — cada um recorre ao transporte Guzzle ausente. Existe exatamente um ponto de construção, no ClientFactory:

$config = new Assinafy\SDK\Configuration( $api_key, $account_id, $base_url, 30, 10 );
$client = new Assinafy\SDK\AssinafyClient( $config, new Assinafy\WP\Http\WpHttpClient( $config ) );

Passar por wp_remote_request() também entrega de graça o proxy configurado no site (WP_PROXY_*), o respeito a WP_HTTP_BLOCK_EXTERNAL, as configurações de SSL do próprio site e a superfície de filtros http_request_* já existente.

Gerando o zip de distribuição

composer install  # Includes Strauss, the development tool that builds vendor-prefixed/.
bin/build-zip.sh
# Built /path/to/dist/assinafy-1.0.0.zip

O bin/build-zip.sh aplica o .distignore e depois se recusa a produzir um zip a menos que o cabeçalho Version: do plugin, a constante de runtime ASSINAFY_VERSION e o Stable tag: do readme concordem, nenhum namespace GuzzleHttp apareça em qualquer lugar da árvore preparada, o vendor-prefixed/autoload.php tenha sobrevivido e a árvore vendor/ sem prefixo não. Defina ASSINAFY_DIST_DIR para gerar o build em outro lugar que não dist/.


3. Configuração

As configurações ficam em Assinafy → Configurações (capability manage_options).

Configuração Option Padrão
Ambiente assinafy_environment production
ID da conta assinafy_account_id ''
Chave de API assinafy_api_key_enc '' (armazenada criptografada)
Aceitar entregas de webhook assinafy_webhook_enabled false
Token do endpoint de webhook assinafy_webhook_token gerado na ativação
Prazo para assinatura (dias) assinafy_default_expiry_days 30
Mensagem aos signatários assinafy_default_message ''
Capability exigida para enviar assinafy_sender_cap assinafy_send
Apagar dados ao desinstalar assinafy_delete_data_on_uninstall false

Settings::OPTIONS é o registro único de onde tudo isso é lido — o registro das options, a tela e o uninstall.php iteram o mesmo mapa, então uma option não pode ser adicionada em um lugar e esquecida em outro.

Credenciais

A chave de API é criptografada em repouso com libsodium. O blob armazenado é hex( version-byte || nonce || secretbox ); o byte de versão inicial existe para que uma futura mudança na derivação da chave seja detectável, em vez de produzir lixo silenciosamente.

O campo é renderizado com value="" para que o texto cifrado nunca chegue ao navegador, o que significa que todo salvamento que não muda a chave chega em branco. Em branco significa manter — um filtro pre_update_option_assinafy_api_key_enc restaura o valor armazenado.

Uma falha de descriptografia retorna um WP_Error distinguível, nunca uma string vazia:

Código de erro Significado
assinafy_credentials_unreadable O blob não descriptografa. Quase sempre uma rotação de salts. Informe a chave de novo.
assinafy_credentials_key_version Escrito por outra versão de derivação de chave. Informe a chave de novo.

Se esses casos retornassem '', um site cujos salts fossem rotacionados se reportaria como "não configurado" e a causa real nunca apareceria.

Constantes no wp-config.php

Constantes ASSINAFY_API_KEY e ASSINAFY_ACCOUNT_ID com string não vazia sobrepõem as options salvas e deixam esses dois campos da tela somente leitura. ASSINAFY_ENCRYPTION_KEY fornece, opcionalmente, o material de chave para criptografia; ela não tem campo na tela de configurações.

// wp-config.php

/** API key, taking precedence over the encrypted option. */
define( 'ASSINAFY_API_KEY', '{API_KEY}' );

/** Account id, taking precedence over the stored option. */
define( 'ASSINAFY_ACCOUNT_ID', '{ACCOUNT_ID}' );

/**
 * Key material for encrypting the stored API key. Optional.
 * Without it the key is derived from wp_salt( 'logged_in' ), which means
 * rotating the site salts invalidates the stored API key.
 */
define( 'ASSINAFY_ENCRYPTION_KEY', 'a long random string, generated once, never committed' );

ASSINAFY_ENCRYPTION_KEY é a opção a usar se o seu deploy rotaciona salts, ou se você quer o material de chave completamente fora do banco de dados. Ela passa por sodium_crypto_generichash até 32 bytes, então o comprimento dela não importa — a entropia sim.

Com ASSINAFY_API_KEY e ASSINAFY_ACCOUNT_ID configurados, as credenciais não precisam ficar armazenadas no banco. Definir as constantes não apaga credenciais salvas anteriormente. Tanto o envio quanto a checagem de conta nos webhooks respeitam essas sobreposições.

Escolha do ambiente

A option de ambiente mapeia para uma base URL a partir das constantes do próprio SDK. Ela nunca é um campo de texto livre — uma URL que o usuário pode digitar é um convite a apontar uma requisição autenticada para outra origem.

Configuração Base URL Host dos links de assinatura
production (padrão) https://api.assinafy.com.br/v1 app.assinafy.com.br
sandbox https://sandbox.assinafy.com.br/v1 app-sandbox.assinafy.com.br

Documentos do ambiente de testes (sandbox) não têm efeito legal e são cobrados separadamente.

Capabilities

Três capabilities personalizadas são instaladas na ativação:

Capability Concedida a Protege
assinafy_send administrator, editor A tela de envio, reenviar, estender, renomear
assinafy_manage administrator Cancelar
assinafy_view administrator, editor, author O menu Assinafy, a lista de documentos, os downloads

O post type do documento declara capability_type => array( 'assinafy_document', 'assinafy_documents' ) com map_meta_cap => true, e Capabilities::map_meta_cap() reescreve as primitivas expandidas (edit_assinafy_documents, delete_others_assinafy_documents, …) sobre essas três. Um post type deixado em capability_type => 'post' mapearia tudo de volta para edit_posts e as capabilities personalizadas não protegeriam absolutamente nada.

A capability de compor/enviar no admin é configurável por assinafy_sender_cap. Reenviar, estender e renomear continuam exigindo assinafy_send. Essas permissões valem para todos os registros Assinafy do site, não apenas para os registros criados pelo usuário atual. Integrações diretas em PHP precisam impor a própria autorização do host antes de chamar o serviço ou as ações de envio.


4. O fluxo do documento, passo a passo

O caminho principal do plugin: método virtual, verificação por e-mail, todos os signatários em paralelo na etapa 1. Ele vai da validação local à precificação, à resolução de signatários, ao upload, à criação do assignment, aos links de assinatura, à sincronização de status e à entrega do PDF assinado. Os passos 0–4 rodam através de SendService::send(); os chamadores do admin, dos hooks, do WooCommerce, da CLI e do cron compartilham o mesmo serviço de envio.

Cada passo, com as requisições e respostas exatas, está documentado em docs/document-flow.md.


5. Pontos de extensão

Os adaptadores se registram por assinafy_ready( $send, $records ) depois do boot do core e usam o serviço de envio compartilhado, os acessores de documento e as actions nativas abaixo. Veja docs/integrations.md para propriedade, roteamento de origem e fronteiras entre pacotes.

assinafy_send_document — enviar agora

O ponto de entrada universal. Qualquer tema, plugin de formulário ou integração sob medida pode solicitar uma assinatura com uma linha e sem acoplamento às classes deste plugin.

do_action(
    'assinafy_send_document',
    array(
        'attachment_id'   => 412,
        'signers'         => array(
            array( 'full_name' => 'Jane Doe', 'email' => 'jane@example.com' ),
        ),
        'message'         => 'Please review and sign the attached agreement.',
        'idempotency_key' => 'contact-form-7-entry-1187',
    )
);

A action roda de forma síncrona. A quantidade de requisições e a duração dependem da estimativa de custo, da busca ou criação de signatários, do upload, do assignment e de haver ou não um envio anterior sendo retomado.

assinafy_send_document_async — enviar no próximo tick do cron

Os mesmos $args, agendados com wp_schedule_single_event() e retornando imediatamente. Use a partir de uma requisição de front-end, de um callback de pagamento ou de qualquer coisa que não possa ficar bloqueada por uma API de terceiros.

do_action( 'assinafy_send_document_async', $args );

O evento agendado dispara novamente assinafy_send_document, então o caminho adiado passa exatamente pelo mesmo handler do caminho inline, e qualquer listener que você tenha adicionado à action pública também enxerga o envio adiado. O WordPress ainda recusa um segundo agendamento idêntico dentro de dez minutos, o que é uma camada extra e gratuita de proteção contra envio duplicado.

O contrato de $args

Ambas as actions recebem exatamente um argumento, um array associativo, repassado a SendService::send() sem alteração.

Informe attachment_id ou file_path; um ID de anexo positivo tem precedência quando ambos são fornecidos pela API PHP. A CLI rejeita o envio dos dois.

Chave Tipo Observações
attachment_id int Anexo da biblioteca de mídia cujo post_mime_type é application/pdf.
file_path string Caminho absoluto para um PDF legível neste servidor.

Também obrigatório:

Chave Tipo Observações
signers array<int, array> Pelo menos uma entrada.

Cada linha de signatário aceita:

Chave Tipo Observações
full_name (ou name) string Obrigatório, a menos que id seja informado.
email string Obrigatório para verificação por Email, a menos que um ID de signatário existente seja informado. Linhas só com telefone assumem Whatsapp.
whatsapp_phone_number (ou phone) string E.164. Números nacionais crus de 10/11 dígitos recebem +55.
step int Informe para todos os signatários ou omita para todos. O padrão é 1; etapas explícitas começam em 1 sem lacunas. Signatários DigitalCertificate precisam de uma etapa só deles.
verification_method string Email, Whatsapp ou DigitalCertificate. DigitalCertificate exige um ID de signatário existente com documento de identificação configurado na Assinafy.
notification_methods array<string> Exatamente um canal Email ou Whatsapp. A verificação por Email/Whatsapp exige o canal correspondente; DigitalCertificate aceita qualquer um dos dois.
id string Um id de signatário Assinafy existente, usado como está em vez do buscar-e-criar.

Chaves opcionais de nível superior:

Chave Tipo Observações
message string Corpo do convite. Recorre a assinafy_default_message.
expires_at string ISO 8601 com Z ou um deslocamento ±HH:MM. Recorre à janela de expiração configurada.
post_id int Um registro assinafy_document vazio para reaproveitar. Histórico remoto de assinatura existente nunca é sobrescrito.
source array {integration: string, record_id: string} opcional, identificando o registro no host. Veja o schema exato e as regras de retentativa.
idempotency_key string Identificador estável para este envio.

Derive a idempotency_key do evento que torna o envio único — por exemplo, um id de pedido e um id de anexo. Reutilize-a em retentativas; use uma chave nova para um novo envio intencional. A chave tem escopo na conta e no ambiente configurados; adaptadores precisam incluir seu provedor, registro e workflow nas chaves fornecidas. Sem uma chave explícita, o plugin gera um hash do conteúdo do PDF, dos signatários normalizados, do post de destino, do autor, da mensagem, da origem e da política de expiração. Chaves concluídas permanecem vinculadas aos seus registros depois que o transient de cinco minutos expira, e envios parciais são retomados contra o upload salvo. O formulário de composição do admin gera um id de requisição por formulário, mantido entre submissões duplicadas.

assinafy_send_document_result — observar o resultado

Dispara depois de qualquer envio via hook, inline ou adiado, com o id do post espelho ou um WP_Error. Chamadas diretas a SendService::send() retornam o resultado para quem chamou.

add_action(
    'assinafy_send_document_result',
    function ( $result, array $args ) {
        if ( is_wp_error( $result ) ) {
            error_log( 'Assinafy send failed: ' . $result->get_error_message() );
            return;
        }

        // $result is the local Assinafy mirror ID, not the source entry/order ID.
        // An adapter can route its host update using $args['source'].
        error_log( sprintf( 'Assinafy document record: %d', $result ) );
    },
    10,
    2
);

O wrapper do hook reporta erros do serviço de envio e exceções capturadas por essa action de resultado. Handlers de resultado devem tratar erros sem lançar exceções. Erros de validação antecipada e de agendamento podem ser reportados sem entrada no log; erros de agendamento do WordPress Cron também podem chegar a essa action.

Códigos de WP_Error nos quais você pode ramificar:

Código Significado
assinafy_send_invalid_args $args não era um array.
assinafy_send_no_document Nem file_path nem attachment_id.
assinafy_send_no_signers signers ausente ou vazio.
assinafy_not_configured Falta a chave de API ou o ID da conta.
assinafy_not_a_pdf O mime type do anexo não é application/pdf.
assinafy_file_missing O anexo não tem arquivo em disco.
assinafy_no_file Nenhuma origem de arquivo foi resolvida.
assinafy_invalid_pdf A checagem local de assertUploadable() falhou.
assinafy_signer_incomplete Campos de signatário malformados, métodos/canais incompatíveis, identidades duplicadas ou etapas de assinatura inválidas.
assinafy_signer_email Um endereço de e-mail de signatário não é válido.
assinafy_signer_contact Um signatário não tem e-mail, nem telefone, nem id.
assinafy_send_in_progress O lock está retido por um envio em andamento.
assinafy_insufficient_resources has_sufficient_resources: false, carregando a mensagem de blocking_reason.
assinafy_plan_restricted 403 do estimate-cost — o método não está neste plano.
assinafy_send_failed Falha de workflow, de API, de persistência ou de upload incerto. Os dados do erro podem conter um post_id de recuperação.
assinafy_invalid_source A origem não corresponde ao schema documentado.
assinafy_source_conflict A origem não pode ser salva ou difere da requisição existente.
assinafy_invalid_expiry Prazo inválido ou no passado.
assinafy_credentials_key_version A credencial armazenada usa uma versão de chave não suportada.
assinafy_credentials_unreadable A credencial armazenada não pode ser descriptografada.
assinafy_client_unavailable Falha ao construir o client.

assinafy_document_status_changed — toda transição

Dispara a partir do StatusSync quando o cron, um webhook, a CLI ou um refresh no admin encontra um status diferente do valor armazenado. Um signatário pode progredir sem mudar o status do documento; isso sozinho não dispara esta action.

add_action(
    'assinafy_document_status_changed',
    function ( int $post_id, string $current, string $previous ) {
        if ( 'pending_signature' === $current && 'uploaded' === $previous ) {
            // Assinafy finished rendering pages and released the invitations.
        }
    },
    10,
    3
);

$previous só fica vazio quando nenhum status havia sido espelhado antes. Uploads normalmente registram seu status inicial antes da primeira sincronização posterior.

Os quatro hooks terminais

Estas actions acompanham uma transição detectada para um status terminal, junto de assinafy_document_status_changed. Elas não são notificações duráveis nem exatamente-uma-vez; os handlers precisam ser idempotentes. O segundo argumento é o documento exatamente como a API o retornou — o payload completo do Passo 6, com assignment e pages incluídos.

Action Dispara no status
assinafy_document_certificated certificated
assinafy_document_rejected rejected_by_signer ou rejected_by_user
assinafy_document_expired expired
assinafy_document_failed failed
add_action(
    'assinafy_document_certificated',
    function ( int $post_id, array $document ) {
        // Artifact endpoints require account authentication; read their names here.
        $artifacts = array_keys( (array) ( $document['artifacts'] ?? array() ) );

        if ( in_array( 'certificated', $artifacts, true ) ) {
            wp_mail(
                get_option( 'admin_email' ),
                'Signed: ' . get_the_title( $post_id ),
                admin_url( 'post.php?post=' . $post_id . '&action=edit' )
            );
        }
    },
    10,
    2
);

add_action(
    'assinafy_document_rejected',
    function ( int $post_id, array $document ) {
        // decline_reason and declined_by are populated on a signer decline.
        $reason = (string) ( $document['decline_reason'] ?? '' );

        error_log( sprintf( 'Assinafy document %d declined: %s', $post_id, $reason ) );
    },
    10,
    2
);

Não existe hook de "todos assinaram", porque não existe esse status: a última assinatura move o documento para certificating e depois para certificated.

assinafy_reconcile — o hook do cron

Plugin::CRON_HOOK, agendado de hora em hora na ativação e ligado a StatusSync::reconcile(). Dispare você mesmo para forçar uma varredura:

do_action( 'assinafy_reconcile' );

Ou desagende o evento do WP-Cron e o conduza por um timer do sistema — veja a §6.

Lendo um registro

O DocumentRecord declara as meta keys do espelho do documento e fornece os acessores suportados. As chaves de produto/pedido do WooCommerce pertencem ao seu adaptador. Não leia as metas diretamente; a maioria dos nomes de chave é privada e os formatos JSON não fazem parte do contrato. Para encontrar um registro, em vez de ler um, use o DocumentIndex.

$records = new Assinafy\WP\Documents\DocumentRecord();

$records->document_id( $post_id );    // string, '' when not yet sent
$records->status( $post_id );         // one of the eleven status codes
$records->is_closed( $post_id );      // bool
$records->assignment_id( $post_id );  // string
$records->artifacts( $post_id );      // array<int, string> — NAMES, never URLs
$records->synced_at( $post_id );      // int, last hydration or reconciliation visit, including a failed visit
$records->last_error( $post_id );     // string, last recorded send/sync error
$records->source( $post_id );         // integration + record_id, or array() for unattributed records

foreach ( $records->signers( $post_id ) as $signer ) {
    // id, name, email, step, notified, completed, signing_url
    echo esc_html( $signer['name'] ), ' — ', $signer['completed'] ? 'signed' : 'pending';
}

Para downloads autenticados de artefatos, use o construtor de URL do proxy. Ele inclui o nonce; as permissões são verificadas quando o link é seguido:

$url = Assinafy\WP\Documents\DownloadProxy::url( $post_id, 'certificated' );

6. WP-CLI

Registrado apenas quando o WP-CLI está carregado. Quatro subcomandos, todos funcionais; não há stubs.

wp assinafy status

Reporta como este site está conectado. Contatar a conta custa uma requisição; ler a assinatura do plano custa uma segunda.

$ wp assinafy status
+----------------------+---------------------------------------------------------------+
| field                | value                                                         |
+----------------------+---------------------------------------------------------------+
| Environment          | production                                                    |
| API base URL         | https://api.assinafy.com.br/v1                                |
| Credentials          | configured                                                    |
| Account              | Acme Inc. ({ACCOUNT_ID})                                      |
| This site endpoint   | https://example.com/wp-json/assinafy/v1/webhook/<token>       |
| Webhook subscription | https://example.com/wp-json/assinafy/v1/webhook/<token>       |
| Webhook delivering   | yes                                                           |
| Webhook events       | document_metadata_ready, document_ready, signer_signed_doc…   |
| Rate budget          | 117 requests left, window resets in 44s (read 3s ago)         |
+----------------------+---------------------------------------------------------------+

[--format=<table|json|csv|yaml>]. Uma credencial não configurada ou ilegível aparece na linha Credentials em vez de fazer o comando falhar, então a saída continua útil em um health check:

wp assinafy status --format=json | jq -e 'any(.[]; .field=="Account" and (.value | startswith("unreachable: ") | not))'

wp assinafy send

$ wp assinafy send --file=/srv/contracts/nda.pdf --signers="Jane Doe <jane@example.com>"
Success: Sent. Local record: post 4187.
Opção Observações
--file=<path> Caminho do PDF. Mutuamente exclusivo com --attachment.
--attachment=<id> Id do anexo na biblioteca de mídia. Mutuamente exclusivo com --file.
--signers=<list> Separados por vírgula. Cada entrada é Full Name <address@example.com> ou apenas um endereço, caso em que o endereço também serve de nome.
--message=<text> Usa por padrão a mensagem configurada.
--expires=<datetime> ISO 8601 com Z ou ±HH:MM, por exemplo 2026-12-31T23:59:59Z.
--key=<idempotency-key> Repetir o comando com a mesma chave reutiliza ou retoma o envio registrado. Use uma chave nova para um novo contrato/versão intencional.
--porcelain Imprime o ID do registro Assinafy local, inclusive um ID existente em um envio deduplicado.

A chave padrão da CLI faz o hash do caminho/anexo selecionado e dos signatários interpretados. Ela não inclui mudanças nos bytes do PDF, na mensagem ou na expiração. Use uma --key nova e explícita para um novo envio intencional quando esses detalhes mudarem, e reutilize essa chave em retentativas.

# Two signers, in parallel on step 1.
wp assinafy send --attachment=412 --signers="jane@example.com,sam@example.com"

# Capture the post id for a shell script.
POST=$(wp assinafy send --attachment=412 --signers=jane@example.com --porcelain)

wp assinafy sync

$ wp assinafy sync
Success: Reconcile pass finished.

$ wp assinafy sync 104618b275d321f5de22240ebfda
Success: Refreshed 104618b275d321f5de22240ebfda.

Sem argumento, este comando roda a mesma passada que o cron horário roda. Com um id de documento, ele relê aquele documento. De um jeito ou de outro, o estado local é escrito a partir da resposta da API, nunca de outra coisa.

Uma varredura geral considera no máximo 20 registros abertos e para quando o orçamento restante de API registrado cai abaixo de 30. Um ID de documento específico precisa já ter um espelho local.

O wp assinafy sync faz apenas reconciliação; ele não executa envios enfileirados nem outros jobs do WordPress. Ao substituir o WP-Cron disparado por requisições por um timer do sistema, rode todos os eventos vencidos:

# wp-config.php: define( 'DISABLE_WP_CRON', true );
# crontab, every minute:
* * * * * cd /srv/site && wp cron event run --due-now --quiet

wp assinafy webhook <status|register|off>

$ wp assinafy webhook status
This site endpoint: https://example.com/wp-json/assinafy/v1/webhook/<token>
Registered URL:    https://other-site.example.com/hooks/assinafy
Delivering:        no
Failure alerts to: ops@example.com
Events:            document_ready, signer_signed_document, signer_rejected_document
Last changed:      2026-08-27T17:55:12Z
Warning: The account delivers somewhere else. This site will not receive webhooks until it is registered.

$ wp assinafy webhook register
This account delivers to https://other-site.example.com/hooks/assinafy. Replace it with this site? [y/n] y
Success: Assinafy now delivers to https://example.com/wp-json/assinafy/v1/webhook/<token>

$ wp assinafy webhook off
Success: Deliveries stopped. The subscription stays on file and can be registered again.
Opção Observações
--email=<address> Endereço que a Assinafy alerta quando uma entrega falha. Usa por padrão o e-mail do administrador do site.
--yes Responde ao prompt de tomada de controle sem perguntar. Use em uma migração não assistida.

O register inscreve nos eventos document_metadata_ready, document_ready, signer_signed_document, signer_viewed_document, signer_rejected_document, user_rejected_document e document_processing_failed, e define assinafy_webhook_enabled para que a rota passe a aceitar entregas. O off chama a rota de inativação e limpa essa option — não existe rota DELETE para uma inscrição. Essa operação desativa a inscrição de toda a conta, inclusive uma que aponte atualmente para outra aplicação.


7. Webhooks

Webhooks são opt-in e opcionais. Eles podem reduzir a latência das atualizações. A reconciliação periódica tenta novamente os documentos espelhados que ainda estão abertos; o tempo depende da execução do cron, do tamanho da fila e da disponibilidade da API.

As entregas não são assinadas

PUT /accounts/{accountId}/webhooks/subscriptions aceita exatamente quatro chaves — events, is_active, url, email. Não há campo de segredo, nem cabeçalho HMAC, nem assinatura de qualquer tipo no contrato de API suportado pelo SDK embutido.

Este plugin nunca alega ter webhooks verificados por HMAC e nunca confia no corpo de uma entrega. O modelo de segurança

This README is longer than the copy stored here. Read the rest on GitHub →

Releases

1 release. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v1.0.0 latest
Sep 14, 2026 3d ago
assinafy-1.0.0.zip
0