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
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.zipAssinafy 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.
- 1. O que este plugin faz
- 2. Requisitos e instalação
- 3. Configuração
- 4. O fluxo do documento, passo a passo
- 5. Pontos de extensão
- 6. WP-CLI
- 7. Webhooks
- 8. WooCommerce
- 9. Referência de chamadas do SDK
- 10. Solução de problemas
- 11. Desenvolvimento
- Comportamento em multisite e privacidade
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:
- 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.
- 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.
- Os endpoints de artefato exigem autenticação da conta. O plugin guarda os nomes dos
artefatos e usa o
DownloadProxypara 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.