{"id":309417,"date":"2026-05-22T23:27:19","date_gmt":"2026-05-22T23:27:19","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/signdocs-brasil\/"},"modified":"2026-05-22T23:26:46","modified_gmt":"2026-05-22T23:26:46","slug":"signdocs-brasil","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/signdocs-brasil\/","author":23494676,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.3.7","stable_tag":"1.3.7","tested":"6.9.4","requires":"6.0","requires_php":"8.1","requires_plugins":null,"header_name":"SignDocs Brasil","header_author":"SignDocs Brasil","header_description":"Assinatura digital e eletr\u00f4nica integrada ao seu site WordPress via SignDocs Brasil.","assets_banners_color":"0b2545","last_updated":"2026-05-22 23:26:46","external_support_url":"","external_repository_url":"","donate_link":"https:\/\/signdocs.com.br","header_plugin_uri":"https:\/\/github.com\/signdocsbrasil\/signdocs-brasil-wordpress","header_author_uri":"https:\/\/signdocs.com.br","rating":0,"author_block_rating":0,"active_installs":0,"downloads":19,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.3.7":{"tag":"1.3.7","author":"signdocsbrasil","date":"2026-05-22 23:26:46"}},"upgrade_notice":{"1.3.7":"<p>Reviewer round 2: new &quot;External services&quot; readme section with Terms of Use + Privacy Policy links per endpoint; trusted-proxy IP validates via <code>FILTER_VALIDATE_IP<\/code>; allow-list on policy\/locale; <code>esc_html<\/code> on exception messages. No behavior changes.<\/p>","1.3.6":"<p>WP.org reviewer feedback round 1: rephrased one comparative description sentence and refactored the webhook REST route to use a proper <code>permission_callback<\/code> that performs HMAC verification (instead of <code>__return_true<\/code> with HMAC checked in the handler). No runtime behavior changes for callers.<\/p>","1.3.5":"<p>Plugin URI \/ Author URI differentiation for WP.org submission auto-scanner. Plugin URI now points to the GitHub repo (specific to this plugin); Author URI remains the company site. No behavior changes.<\/p>","1.3.4":"<p>Plugin Check (PCP) hardening pass for WP.org submission. No behavior changes \u2014 defensive <code>wp_unslash()<\/code> on <code>$_POST<\/code> reads, documented <code>phpcs:ignore<\/code> annotations on the audit-log custom-table queries, dropped the unused <code>Domain Path<\/code> header.<\/p>","1.3.3":"<p>Cleanup pass: <code>wp signdocs webhook-test<\/code> now prints actual delivery status; logger writes a row on every successful <code>TRANSACTION.COMPLETED<\/code>; legacy <code>SIGNING_SESSION.*<\/code> dispatch branches removed (server only emits <code>TRANSACTION.*<\/code> events).<\/p>","1.3.2":"<p>Two production-blocking fixes: webhook dedup was keyed off the subscription ID (so <code>TRANSACTION.COMPLETED<\/code> never updated CPTs) and custom capabilities resolved to <code>do_not_allow<\/code> (locking admins out of the Verify page). Strongly recommended for any 1.3.x install.<\/p>","1.3.0":"<p>Requires PHP SDK 1.4 (<code>composer update<\/code>); fixes the <code>CreateSigningSessionRequest<\/code> shape that returned 400 with SDK 1.3.x. Also adds CPF \/ CNPJ collection at every entry point \u2014 required by the API. Re-check custom integrations.<\/p>","1.2.0":"<p>Adds multi-signer envelopes, the verification page, the audit log, Private Key JWT authentication, and webhook secret rotation. Plugin re-activation is required to grant the new capabilities (<code>signdocs_manage<\/code> \/ <code>_send<\/code> \/ <code>_verify<\/code> \/ <code>_view_logs<\/code>) to existing roles.<\/p>","1.1.0":"<p>Hardening update + SDK 1.3.0 alignment. Re-activation is required to create the audit log table and install the custom capabilities.<\/p>","1.0.0":"<p>Initial release of the SignDocs Brasil WordPress plugin.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3544547,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3544547,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3544547,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3544547,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":{"signdocs-brasil\/signing-button":{"$schema":"https:\/\/schemas.wp.org\/trunk\/block.json","apiVersion":3,"name":"signdocs-brasil\/signing-button","version":"1.0.0","title":"SignDocs Assinatura","category":"widgets","icon":"media-document","description":"Bot\u00e3o de assinatura eletr\u00f4nica via SignDocs Brasil.","textdomain":"signdocs-brasil","attributes":{"document_id":{"type":"number","default":0},"policy":{"type":"string","default":""},"locale":{"type":"string","default":""},"mode":{"type":"string","default":""},"button_text":{"type":"string","default":"Assinar Documento"},"return_url":{"type":"string","default":""},"show_form":{"type":"string","default":"false"}},"supports":{"html":false,"align":true},"editorScript":"file:.\/index.js"}},"tagged_versions":["1.3.7"],"block_files":[],"assets_screenshots":[],"screenshots":{"1":"Settings page \u2014 credentials, environment, authentication method (client_secret or Private Key JWT), signing defaults","2":"Gutenberg block in the editor with the configuration panel on the right","3":"Shortcode rendered with the signing button on the front-end","4":"Signing popup opened by the <code>@signdocs-brasil\/js<\/code> SDK on <code>sign.signdocs.com.br<\/code>","5":"Signatures list in the admin panel with colored status, signer, policy","6":"\"Verify Document\" page \u2014 evidence ID pasted, result with signers, tenant CNPJ, downloads","7":"Multi-signer envelope panel with the signer repeater and sequential \/ parallel toggle","8":"Audit log (WP_List_Table) with level \/ event \/ date filters and the CSV export button","9":"WooCommerce product tab with required-signature configuration","10":"WooCommerce order email with the signing link"}},"plugin_section":[],"plugin_tags":[264060,24307,26327,264061,286],"plugin_category":[45],"plugin_contributors":[264062],"plugin_business_model":[],"class_list":["post-309417","plugin","type-plugin","status-publish","hentry","plugin_tags-contracts","plugin_tags-digital-signature","plugin_tags-electronic-signature","plugin_tags-icp-brasil","plugin_tags-woocommerce","plugin_category-ecommerce","plugin_contributors-signdocsbrasil","plugin_committers-signdocsbrasil"],"banners":{"banner":"https:\/\/ps.w.org\/signdocs-brasil\/assets\/banner-772x250.png?rev=3544547","banner_2x":"https:\/\/ps.w.org\/signdocs-brasil\/assets\/banner-1544x500.png?rev=3544547","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/signdocs-brasil\/assets\/icon-128x128.png?rev=3544547","icon_2x":"https:\/\/ps.w.org\/signdocs-brasil\/assets\/icon-256x256.png?rev=3544547","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>SignDocs Brasil is the official WordPress plugin for <strong>legally-binding electronic signatures in Brazil<\/strong>. Embed signing flows on any page with a shortcode or Gutenberg block, send multi-signer envelopes (sequential or parallel), verify signed evidence directly from the WordPress admin, and track everything through an audit log with CSV export.<\/p>\n\n<p>Built on top of the official SignDocs Brasil PHP SDK (<code>signdocs-brasil\/signdocs-brasil-php<\/code>), the plugin leverages OAuth token caching shared across PHP-FPM workers, deterministic idempotency, webhook secret rotation with a grace window, and observability via <code>RateLimit-*<\/code> \/ <code>Deprecation<\/code> \/ <code>Sunset<\/code> response headers.<\/p>\n\n<p>The plugin targets the Brazilian market (compliance with MP 2.200-2\/2001, ICP-Brasil, NT65\/ITI for INSS payroll loans), but works for any signing workflow worldwide. The signing UI itself is hosted on <code>sign.signdocs.com.br<\/code>, isolated from your WordPress install, so a compromised WordPress site cannot forge signatures.<\/p>\n\n<h4>Why SignDocs Brasil?<\/h4>\n\n<ul>\n<li><strong>Brazilian compliance<\/strong> \u2014 MP 2.200-2\/2001, PKCS#7\/CMS evidence package, ICP-Brasil A1\/A3 certificate support, NT65\/ITI flow for INSS payroll loans<\/li>\n<li><strong>Seven verification policies<\/strong> \u2014 CLICK_ONLY, CLICK_PLUS_OTP, BIOMETRIC, BIOMETRIC_PLUS_OTP, DIGITAL_CERTIFICATE, BIOMETRIC_SERPRO, BIOMETRIC_SERPRO_AUTO_FALLBACK<\/li>\n<li><strong>Multi-signer envelopes<\/strong> \u2014 sequential (each signer waits for the previous one) or parallel (everyone signs simultaneously), with consolidated <code>.p7s<\/code> or combined PDF download when complete<\/li>\n<li><strong>Two authentication modes<\/strong> \u2014 OAuth2 <code>client_credentials<\/code> (simple) or Private Key JWT ES256 (for regulated customers who cannot store shared secrets at rest)<\/li>\n<li><strong>WooCommerce integration<\/strong> \u2014 automatically emails the signing link after order completion<\/li>\n<li><strong>Complete audit trail<\/strong> \u2014 every API call and webhook delivery is logged in a dedicated table with a filterable WP_List_Table view and CSV export<\/li>\n<li><strong>GDPR \/ LGPD<\/strong> \u2014 data exporter and eraser handlers registered with the WordPress privacy panel<\/li>\n<li><strong>Observability<\/strong> \u2014 <code>RateLimit-*<\/code> headers captured for the dashboard widget; deprecation warnings (RFC 8594 <code>Deprecation<\/code> \/ <code>Sunset<\/code>) surface as admin notices<\/li>\n<li><strong>Zero code<\/strong> \u2014 configure everything from the WordPress admin<\/li>\n<\/ul>\n\n<h4>Features<\/h4>\n\n<ul>\n<li>Shortcode <code>[signdocs]<\/code> and Gutenberg block to embed the signing button on any post or page<\/li>\n<li>Custom post type <code>signdocs_envelope<\/code> for multi-signer workflows with a signer repeater<\/li>\n<li>\"Verify Document\" admin page \u2014 paste an evidence ID or envelope ID and inspect signer identities, tenant CNPJ, consolidated downloads<\/li>\n<li>Audit log with filters by level, event type, and date range, plus streaming CSV export (via <code>php:\/\/output<\/code>, safe for multi-GB exports)<\/li>\n<li>Webhook secret rotation with a 7-day grace window \u2014 both secrets (current + previous) are accepted during rotation<\/li>\n<li>All 17 webhook event types covered, including the NT65 events (<code>STEP.PURPOSE_DISCLOSURE_SENT<\/code>, <code>TRANSACTION.DEADLINE_APPROACHING<\/code>)<\/li>\n<li>Custom capabilities (<code>signdocs_manage<\/code>, <code>signdocs_send<\/code>, <code>signdocs_verify<\/code>, <code>signdocs_view_logs<\/code>) automatically granted to administrator \/ editor \/ author<\/li>\n<li>WP-CLI commands (<code>wp signdocs health | send | status | webhook-test | log-tail<\/code>) for shell automation<\/li>\n<li>WooCommerce integration \u2014 \"SignDocs Signature\" product tab, automatic email with the signing link, order notes after completion<\/li>\n<li>Popup, redirect, or overlay \u2014 pick the embed mode that fits your theme<\/li>\n<li>Optional anonymous signing with rate limiting<\/li>\n<li>Credentials encrypted with AES-256-CBC in <code>wp_options<\/code><\/li>\n<li>Hardened webhook receiver: timestamp drift gate (\u2264300s), HMAC-SHA256 timing-safe verification, replay de-duplication via <code>X-SignDocs-Webhook-Id<\/code><\/li>\n<li>OAuth token cache shared via WordPress transients (<code>WpTransientTokenCache<\/code> implements the SDK's <code>TokenCacheInterface<\/code>) \u2014 a single token reused by every PHP-FPM worker<\/li>\n<li>Deterministic idempotency keys on every resource-creating call \u2014 AJAX retries never create duplicate sessions<\/li>\n<li>Deprecation observer (RFC 8594) that surfaces an admin notice when the API signals an endpoint is being removed<\/li>\n<li>Translatable: English, Portuguese (Brazil), Spanish<\/li>\n<\/ul>\n\n<h4>Use cases<\/h4>\n\n<ul>\n<li><strong>Law firms<\/strong> \u2014 powers of attorney, contracts, terms, multi-party envelopes<\/li>\n<li><strong>Real estate<\/strong> \u2014 rental and sale contracts signed by tenant, landlord, and guarantor (sequential envelope)<\/li>\n<li><strong>E-commerce<\/strong> \u2014 terms of service, supplier contracts, post-purchase NDAs<\/li>\n<li><strong>HR and people ops<\/strong> \u2014 employment contracts, NDAs, onboarding paperwork<\/li>\n<li><strong>Education<\/strong> \u2014 enrollment forms and parental consent (parents + student in a parallel envelope)<\/li>\n<li><strong>SaaS<\/strong> \u2014 terms of use and license agreements at onboarding<\/li>\n<li><strong>INSS payroll loans (Brazil-specific)<\/strong> \u2014 NT65 flow with SERPRO biometric verification and purpose disclosure notification<\/li>\n<li><strong>Banks and financial institutions<\/strong> \u2014 Private Key JWT lets you sign without storing a shared secret in the database<\/li>\n<\/ul>\n\n<h4>How it works<\/h4>\n\n<ol>\n<li>Configure your SignDocs Brasil API credentials in the WordPress admin (Client ID + Secret, or Private Key + Key ID)<\/li>\n<li>Add a shortcode, Gutenberg block, or create a multi-signer envelope from the admin<\/li>\n<li>The signer clicks \"Sign Document\" and is redirected to the secure domain <code>sign.signdocs.com.br<\/code> (signing <strong>never happens inside your WordPress site<\/strong> \u2014 this isolates your install from any compromise)<\/li>\n<li>The signer completes the flow according to the configured policy (click, OTP, biometrics, digital certificate)<\/li>\n<li>Webhooks update the status in the WordPress admin in real time; the <code>.p7m<\/code> evidence package becomes available for download and verification<\/li>\n<\/ol>\n\n<h4>Links<\/h4>\n\n<ul>\n<li><a href=\"https:\/\/signdocs.com.br\">Official site<\/a><\/li>\n<li><a href=\"https:\/\/docs.signdocs.com.br\">API documentation<\/a><\/li>\n<li><a href=\"https:\/\/signdocs.com.br\/suporte\">Support<\/a><\/li>\n<li><a href=\"https:\/\/app.signdocs.com.br\/cadastro\">Create a free account<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/signdocsbrasil\/signdocs-brasil-wordpress\">GitHub repository<\/a><\/li>\n<\/ul>\n\n<h3>Usage<\/h3>\n\n<h4>Shortcode<\/h4>\n\n<p>Add to any page or post:<\/p>\n\n<pre><code>[signdocs document_id=\"123\" policy=\"CLICK_ONLY\" button_text=\"Sign Contract\"]\n<\/code><\/pre>\n\n<p>With name \/ email \/ CPF form:<\/p>\n\n<pre><code>[signdocs document_id=\"123\" show_form=\"true\" policy=\"CLICK_PLUS_OTP\"]\n<\/code><\/pre>\n\n<p><strong>Available attributes:<\/strong><\/p>\n\n<ul>\n<li><code>document_id<\/code> (required) \u2014 ID of the PDF attachment in the media library<\/li>\n<li><code>policy<\/code> \u2014 one of: <code>CLICK_ONLY<\/code>, <code>CLICK_PLUS_OTP<\/code>, <code>BIOMETRIC<\/code>, <code>BIOMETRIC_PLUS_OTP<\/code>, <code>DIGITAL_CERTIFICATE<\/code>, <code>BIOMETRIC_SERPRO<\/code>, <code>BIOMETRIC_SERPRO_AUTO_FALLBACK<\/code><\/li>\n<li><code>locale<\/code> \u2014 language: <code>pt-BR<\/code>, <code>en<\/code>, <code>es<\/code><\/li>\n<li><code>mode<\/code> \u2014 embed mode: <code>redirect<\/code> (default), <code>popup<\/code>, <code>overlay<\/code><\/li>\n<li><code>button_text<\/code> \u2014 button label (default: \"Sign Document\")<\/li>\n<li><code>show_form<\/code> \u2014 <code>\"true\"<\/code> to display name \/ email \/ CPF \/ CNPJ inputs<\/li>\n<li><code>return_url<\/code> \u2014 URL to redirect to after signing<\/li>\n<li><code>class<\/code> \u2014 additional CSS class for the button<\/li>\n<\/ul>\n\n<h4>Gutenberg block<\/h4>\n\n<ol>\n<li>In the block editor, click \"+\" to add a block<\/li>\n<li>Search for \"SignDocs\" or \"Signature\"<\/li>\n<li>Pick a PDF in the right sidebar<\/li>\n<li>Configure the policy, locale, and mode<\/li>\n<li>Publish the page<\/li>\n<\/ol>\n\n<h4>Multi-signer envelopes<\/h4>\n\n<p>For contracts with more than one signer (for example, landlord + tenant + guarantor), use the <strong>Envelopes<\/strong> menu:<\/p>\n\n<ol>\n<li>WP Admin &gt; Signatures &gt; Envelopes &gt; Add New<\/li>\n<li>Select the signing mode:\n\n<ul>\n<li><strong>SEQUENTIAL<\/strong> \u2014 each signer signs in order; the next signer only receives their link when the previous one completes<\/li>\n<li><strong>PARALLEL<\/strong> \u2014 all signers can sign simultaneously, in any order<\/li>\n<\/ul><\/li>\n<li>Add the signers (name + email + CPF or CNPJ + optional per-signer policy)<\/li>\n<li>Attach the PDF and publish<\/li>\n<li>Each signer receives an email with their individual link; the admin sees the envelope status update as each signature completes<\/li>\n<li>After everyone has signed, a combined stamped PDF (or consolidated <code>.p7s<\/code> for non-PDF documents) becomes available for download<\/li>\n<\/ol>\n\n<p>The webhook events <code>STEP.STARTED<\/code>, <code>STEP.COMPLETED<\/code>, and <code>STEP.FAILED<\/code> are recorded per signer in each envelope's log.<\/p>\n\n<h4>WooCommerce<\/h4>\n\n<ol>\n<li>Edit a product and open the \"SignDocs Signature\" tab<\/li>\n<li>Check \"Requires signature\" and select the PDF<\/li>\n<li>Configure the verification policy<\/li>\n<li>When an order completes, the signing link is automatically emailed to the customer<\/li>\n<li>After signing, an order note is added with the evidence ID<\/li>\n<\/ol>\n\n<blockquote>\n  <p>The customer's CPF or CNPJ must be present in the order. The plugin reads the standard <code>_billing_cpf<\/code> \/ <code>_billing_cnpj<\/code> order meta keys used by the <a href=\"https:\/\/wordpress.org\/plugins\/woocommerce-extra-checkout-fields-for-brazil\/\">Brazilian Market on WooCommerce<\/a> extension. If neither is present, the plugin adds an order note explaining the requirement and skips session creation.<\/p>\n<\/blockquote>\n\n<h4>Document verification<\/h4>\n\n<p>The <strong>Signatures &gt; Verify<\/strong> page (requires the <code>signdocs_verify<\/code> capability):<\/p>\n\n<ol>\n<li>Paste an <code>evidence_id<\/code> (single signature) or <code>envelope_id<\/code> (multi-signer)<\/li>\n<li>The plugin calls <code>GET \/v1\/verify\/{id}<\/code> or <code>GET \/v1\/verify\/envelope\/{id}<\/code> and renders:\n\n<ul>\n<li>Identities of every signer (name, CPF\/CNPJ)<\/li>\n<li>Tenant CNPJ<\/li>\n<li>Timestamps for each step<\/li>\n<li>The applied policy profile<\/li>\n<li>Download links: evidence package (<code>.p7m<\/code>), signed PDF, consolidated <code>.p7s<\/code> (envelopes), combined PDF (envelopes)<\/li>\n<\/ul><\/li>\n<li>Use the evidence files in external validators (ITI Validador, Adobe Acrobat) for independent confirmation<\/li>\n<\/ol>\n\n<h4>Audit log<\/h4>\n\n<p>The <strong>Signatures &gt; Audit Log<\/strong> page (requires the <code>signdocs_view_logs<\/code> capability):<\/p>\n\n<ul>\n<li>WP_List_Table view over <code>{prefix}signdocs_log<\/code><\/li>\n<li>Filters: level (debug \/ info \/ warning \/ error), event type, date range<\/li>\n<li>CSV export via <code>admin-post.php<\/code> (chunked streaming, safe for multi-GB exports)<\/li>\n<li>Automatic 30-day retention via the daily <code>signdocs_prune_logs<\/code> cron<\/li>\n<li>Every API call, webhook delivery, and deprecation warning is recorded with JSON context<\/li>\n<\/ul>\n\n<h4>WP-CLI<\/h4>\n\n<p>For shell-based operations (useful for automation, CI\/CD, and troubleshooting):<\/p>\n\n<pre><code>wp signdocs health\n<\/code><\/pre>\n\n<p>\u2014 check connectivity to the API in the configured environment<\/p>\n\n<pre><code>wp signdocs send --document=42 --email=alice@example.com --cpf=12345678901 --policy=CLICK_PLUS_OTP\n<\/code><\/pre>\n\n<p>\u2014 create a signing session from a WordPress attachment and print the session ID and URL<\/p>\n\n<pre><code>wp signdocs status &lt;sessionId&gt;\n<\/code><\/pre>\n\n<p>\u2014 look up the status of a session by ID<\/p>\n\n<pre><code>wp signdocs webhook-test &lt;webhookId&gt;\n<\/code><\/pre>\n\n<p>\u2014 send a test delivery to a registered webhook<\/p>\n\n<pre><code>wp signdocs log-tail --level=warning --limit=20\n<\/code><\/pre>\n\n<p>\u2014 show the last N entries of the audit log filtered by level<\/p>\n\n<h4>Webhook secret rotation<\/h4>\n\n<ol>\n<li>In Settings &gt; SignDocs Brasil, click \"Rotate Secret\"<\/li>\n<li>The plugin requests a new secret from the API; the previous secret becomes the \"previous secret\" with a 7-day grace window<\/li>\n<li>During the window, the <code>\/wp-json\/signdocs\/v1\/webhook<\/code> endpoint accepts <strong>both<\/strong> secrets \u2014 in-flight deliveries are not rejected<\/li>\n<li>After 7 days, the daily <code>signdocs_expire_prev_secret<\/code> cron removes the old secret<\/li>\n<li>The rotation status is visible in the admin (with a countdown)<\/li>\n<\/ol>\n\n<h4>For developers<\/h4>\n\n<p><strong>Available hooks:<\/strong><\/p>\n\n<p>Session lifecycle:<\/p>\n\n<ul>\n<li><code>signdocs_session_created<\/code> \u2014 Session created (via the API, not necessarily via WordPress)<\/li>\n<li><code>signdocs_signing_completed<\/code> \u2014 Signing completed successfully<\/li>\n<li><code>signdocs_signing_cancelled<\/code> \u2014 Signing cancelled by the integrator or the signer<\/li>\n<li><code>signdocs_signing_expired<\/code> \u2014 Session expired without completion<\/li>\n<li><code>signdocs_signing_failed<\/code> \u2014 Signing failed (unrecoverable error)<\/li>\n<li><code>signdocs_transaction_fallback<\/code> \u2014 Fallback was triggered (e.g., SERPRO unavailable)<\/li>\n<\/ul>\n\n<p>Per-step (for envelopes and custom flows):<\/p>\n\n<ul>\n<li><code>signdocs_step_started<\/code> \u2014 Step started (OTP sent, biometric capture, etc.)<\/li>\n<li><code>signdocs_step_completed<\/code> \u2014 Step completed<\/li>\n<li><code>signdocs_step_failed<\/code> \u2014 Step failed<\/li>\n<li><code>signdocs_purpose_disclosure_sent<\/code> \u2014 (NT65) Purpose disclosure notification delivered to the beneficiary<\/li>\n<li><code>signdocs_deadline_approaching<\/code> \u2014 (NT65) \u22642 business days left before the INSS submission deadline<\/li>\n<\/ul>\n\n<p>Tenant \/ API:<\/p>\n\n<ul>\n<li><code>signdocs_quota_warning<\/code> \u2014 Tenant usage crossed a threshold (80 \/ 90 \/ 100%)<\/li>\n<li><code>signdocs_api_deprecation_notice<\/code> \u2014 API signaled a deprecated endpoint<\/li>\n<\/ul>\n\n<p>WooCommerce:<\/p>\n\n<ul>\n<li><code>signdocs_wc_signing_completed<\/code> \u2014 A WooCommerce order signing completed<\/li>\n<\/ul>\n\n<p>Each action receives <code>$post_id<\/code> (of the <code>signdocs_signing<\/code> or <code>signdocs_envelope<\/code> CPT) and <code>$payload<\/code> (the raw webhook array) as arguments, except <code>signdocs_quota_warning<\/code> and <code>signdocs_api_deprecation_notice<\/code> which receive only the payload.<\/p>\n\n<p><strong>Capabilities:<\/strong><\/p>\n\n<ul>\n<li><code>signdocs_manage<\/code> \u2014 Configure credentials, webhook, branding; manage other users' envelopes<\/li>\n<li><code>signdocs_send<\/code> \u2014 Create sessions and envelopes<\/li>\n<li><code>signdocs_verify<\/code> \u2014 Use the Verify page and inspect evidence<\/li>\n<li><code>signdocs_view_logs<\/code> \u2014 Access the audit log and export CSV<\/li>\n<\/ul>\n\n<p>Use <code>current_user_can('signdocs_send')<\/code> instead of <code>manage_options<\/code> \/ <code>edit_posts<\/code> when adding custom functionality.<\/p>\n\n<p><strong>PHP SDK:<\/strong><\/p>\n\n<p>The configured SDK client (with encrypted credentials and shared token cache) is available via:<\/p>\n\n<pre><code>$client = Signdocs_Client_Factory::get(); \/\/ SignDocsBrasil\\Api\\SignDocsBrasilClient or null\n<\/code><\/pre>\n\n<p>See the <a href=\"https:\/\/github.com\/signdocsbrasil\/signdocs-brasil-php\">PHP SDK documentation<\/a> for the full surface (transactions, envelopes, verification, users, documentGroups, webhooks, etc.).<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin connects to the SignDocs Brasil platform \u2014 operated by the same company that publishes the plugin \u2014 to create, deliver, and verify electronic signatures. The plugin <strong>cannot function without<\/strong> sending data to these endpoints, because the signing itself happens on the SignDocs servers (the WordPress site only orchestrates the request and stores the result reference).<\/p>\n\n<h4>SignDocs Brasil API (api.signdocs.com.br \/ api-hml.signdocs.com.br)<\/h4>\n\n<p>Used to create signing sessions, register webhooks, verify signed evidence, and manage multi-signer envelopes. The plugin authenticates with the API credentials you enter in the WordPress admin (OAuth2 <code>client_credentials<\/code>, or alternatively Private Key JWT when configured).<\/p>\n\n<ul>\n<li><strong>What data is sent<\/strong>, per signing-session create: the PDF document content (base64-encoded), the signer's name, the signer's email address, the signer's CPF or CNPJ (one is required by the API), the selected verification policy (e.g. <code>CLICK_ONLY<\/code>, <code>BIOMETRIC<\/code>), the language preference, an optional return URL, and metadata fields identifying the WordPress site URL and source surface (shortcode, AJAX, WP-CLI, WooCommerce, envelope).<\/li>\n<li><strong>When<\/strong>: every time a signing session is created. This happens on shortcode AJAX submission, on <code>wp signdocs send<\/code> from the WP-CLI, on WooCommerce order completion when the product is configured for signing, and on every envelope creation \/ new-signer add.<\/li>\n<li><strong>Other API calls<\/strong> that send no document data: webhook registration, status polling (<code>GET \/v1\/signing-sessions\/{id}<\/code>), evidence verification (<code>GET \/v1\/verify\/{evidenceId}<\/code>), envelope status. These send only the relevant identifier you provide (session ID, evidence ID, envelope ID).<\/li>\n<li><strong>Authentication<\/strong>: every API call is authenticated with a short-lived Bearer token obtained from the OAuth2 token endpoint at the same domain (<code>POST {baseUrl}\/oauth2\/token<\/code>). The plugin sends your Client ID and either Client Secret or a signed JWT assertion (when Private Key JWT mode is configured) to that endpoint at first call and again when the cached token expires (typically once per hour per environment); the access token is cached in a WordPress transient and reused across all subsequent API calls. No signer data is sent to the token endpoint.<\/li>\n<li><strong>Environment switch<\/strong>: the plugin uses <code>api-hml.signdocs.com.br<\/code> (HML \/ sandbox) by default, and <code>api.signdocs.com.br<\/code> only when the administrator explicitly switches the environment to \"Production\" in the settings page.<\/li>\n<li>Provided by SignDocs Brasil. <a href=\"https:\/\/signdocs.com.br\/termos-de-uso\">Terms of Use<\/a>. <a href=\"https:\/\/signdocs.com.br\/politica-de-privacidade\">Privacy Policy<\/a>.<\/li>\n<\/ul>\n\n<h4>SignDocs Brasil browser SDK (cdn.signdocs.com.br \/ cdn-hml.signdocs.com.br)<\/h4>\n\n<p>A JavaScript file (<code>signdocs-brasil.js<\/code>) loaded from the SignDocs CDN that opens the signing popup, redirect, or overlay when the signer clicks the embedded \"Sign Document\" button rendered by the shortcode or Gutenberg block.<\/p>\n\n<ul>\n<li><strong>What data is sent<\/strong>: nothing directly by this script load \u2014 it is a static asset request, the same as any other JavaScript file from a third-party CDN. No personally identifiable information is transmitted by the CDN request itself; the script is bytes-identical for every site that loads it.<\/li>\n<li><strong>When<\/strong>: every front-end page-view that renders the <code>[signdocs]<\/code> shortcode or the SignDocs Gutenberg block (the script is enqueued conditionally \u2014 pages without the block do not load it).<\/li>\n<li>The CDN environment (HML vs prod) follows the same <code>signdocs_environment<\/code> option as the API.<\/li>\n<li>Provided by SignDocs Brasil. <a href=\"https:\/\/signdocs.com.br\/termos-de-uso\">Terms of Use<\/a>. <a href=\"https:\/\/signdocs.com.br\/politica-de-privacidade\">Privacy Policy<\/a>.<\/li>\n<\/ul>\n\n<h4>SignDocs Brasil signing UI (sign.signdocs.com.br)<\/h4>\n\n<p>After the signer clicks \"Sign Document\", they are taken to the secure signing page on <code>sign.signdocs.com.br<\/code> \u2014 <strong>not<\/strong> to a page hosted by your WordPress site. The signing flow (OTP, biometric capture, digital-certificate selection, click-only confirmation) executes entirely on this domain. This isolation is intentional: even if your WordPress site were compromised, an attacker could not forge signatures because the authentication factors are collected on a separate origin under SignDocs Brasil's control.<\/p>\n\n<ul>\n<li><strong>What data is sent<\/strong>: the signer interacts directly with this domain to complete the signing flow. The data exchanged here (OTP codes, biometric photos, certificate selections) does not pass through your WordPress site. Your plugin only receives the result back via the webhook described above.<\/li>\n<li><strong>When<\/strong>: when the signer clicks the signing button rendered by the plugin and the browser SDK opens the signing surface (popup \/ redirect \/ overlay).<\/li>\n<li>Provided by SignDocs Brasil. <a href=\"https:\/\/signdocs.com.br\/termos-de-uso\">Terms of Use<\/a>. <a href=\"https:\/\/signdocs.com.br\/politica-de-privacidade\">Privacy Policy<\/a>.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<h4>Automatic install<\/h4>\n\n<ol>\n<li>In the WordPress admin, go to Plugins &gt; Add New<\/li>\n<li>Search for \"SignDocs Brasil\"<\/li>\n<li>Click \"Install\" and then \"Activate\"<\/li>\n<\/ol>\n\n<h4>Manual install<\/h4>\n\n<ol>\n<li>Upload the <code>signdocs-brasil<\/code> folder to <code>\/wp-content\/plugins\/<\/code> (or use Plugins &gt; Upload Plugin with the release ZIP)<\/li>\n<li>Activate the plugin in Plugins &gt; Installed Plugins<\/li>\n<\/ol>\n\n<p>On activation, the plugin:<\/p>\n\n<ul>\n<li>Creates the <code>{prefix}signdocs_log<\/code> table for the audit log<\/li>\n<li>Registers the <code>signdocs_signing<\/code> and <code>signdocs_envelope<\/code> custom post types<\/li>\n<li>Grants the custom capabilities (<code>signdocs_manage<\/code>, <code>signdocs_send<\/code>, <code>signdocs_verify<\/code>, <code>signdocs_view_logs<\/code>) to administrator \/ editor \/ author<\/li>\n<li>Schedules the daily cron jobs for log pruning and rotated-secret expiration<\/li>\n<\/ul>\n\n<h4>Configuration<\/h4>\n\n<ol>\n<li>Open Settings &gt; SignDocs Brasil<\/li>\n<li>Choose the authentication method:\n\n<ul>\n<li><strong>Client Secret<\/strong> (default) \u2014 Client ID + Client Secret obtained from <a href=\"https:\/\/app.signdocs.com.br\">app.signdocs.com.br<\/a><\/li>\n<li><strong>Private Key JWT (ES256)<\/strong> \u2014 PEM-encoded private key + Key ID; the public key is registered separately with SignDocs. Preferred by regulated customers that cannot store a shared secret in the database<\/li>\n<\/ul><\/li>\n<li>Click \"Test Connection\"<\/li>\n<li>Select the environment: HML (sandbox for testing) or Production<\/li>\n<li>Configure the signing defaults (policy, locale, mode, brand color, logo)<\/li>\n<li>Click \"Register Webhook\" \u2014 the plugin calls the API endpoint, receives the HMAC secret, and stores it encrypted<\/li>\n<li>(Optional) Configure <code>signdocs_trusted_proxies<\/code> with trusted CIDR ranges if your site sits behind CloudFront, Cloudflare, or an nginx proxy, so the anonymous-signing rate limiter and audit log see the real client IP<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"do%20i%20need%20a%20signdocs%20brasil%20account%3F\"><h3>Do I need a SignDocs Brasil account?<\/h3><\/dt>\n<dd><p>Yes. <a href=\"https:\/\/app.signdocs.com.br\/cadastro\">Create your free account<\/a> to obtain API credentials. The free plan includes test documents in the HML (sandbox) environment.<\/p><\/dd>\n<dt id=\"are%20these%20signatures%20legally%20binding%3F\"><h3>Are these signatures legally binding?<\/h3><\/dt>\n<dd><p>Yes. SignDocs Brasil electronic signatures comply with Brazilian Provisional Measure 2.200-2\/2001 and produce cryptographic evidence packages (PKCS#7\/CMS) with a complete audit trail. For high-value documents or where ICP-Brasil is required, use the <code>DIGITAL_CERTIFICATE<\/code> policy with the signer's A1 or A3 certificate.<\/p><\/dd>\n<dt id=\"where%20does%20the%20signing%20actually%20happen%3F\"><h3>Where does the signing actually happen?<\/h3><\/dt>\n<dd><p>On the secure domain <code>sign.signdocs.com.br<\/code>, <strong>not inside your WordPress site<\/strong>. The plugin creates the session via the API server-side (credentials never reach the browser), hands a URL + <code>clientSecret<\/code> to the browser, and receives a webhook when complete. This means that even if your WordPress site were compromised, an attacker could not forge signatures \u2014 the authentication flow (OTP, biometrics, certificate) happens on a completely separate domain under SignDocs' control.<\/p><\/dd>\n<dt id=\"how%20do%20multi-signer%20envelopes%20work%3F\"><h3>How do multi-signer envelopes work?<\/h3><\/dt>\n<dd><p>Each envelope has N signers. <strong>SEQUENTIAL<\/strong> mode: the next signer only receives their link after the previous one completes (useful for hierarchical flows like power of attorney \u2192 witness \u2192 notary). <strong>PARALLEL<\/strong> mode: everyone can sign simultaneously (useful for multi-party NDAs, partnership agreements). The envelope admin panel shows each signer's status in real time as the <code>STEP.*<\/code> webhooks arrive. After the last signer, a combined PDF (or consolidated <code>.p7s<\/code> for non-PDFs) becomes available via the Verify page.<\/p><\/dd>\n<dt id=\"can%20i%20use%20this%20without%20storing%20a%20shared%20secret%3F\"><h3>Can I use this without storing a shared secret?<\/h3><\/dt>\n<dd><p>Yes. In the authentication tab of the settings, choose <strong>Private Key JWT (ES256)<\/strong>. You generate an ECDSA P-256 key pair locally, register only the public key with SignDocs (via the <a href=\"https:\/\/app.signdocs.com.br\">app.signdocs.com.br<\/a> panel), and the plugin stores only the PEM private key (encrypted with AES-256-CBC). On every API call the plugin signs a short-lived JWT with the private key \u2014 no shared secret in the database. This mode is required by some regulated customers (banks, fintechs).<\/p><\/dd>\n<dt id=\"what%20does%20the%20audit%20log%20capture%3F\"><h3>What does the audit log capture?<\/h3><\/dt>\n<dd><p>Every API call the plugin makes (method, path, status, duration, rate-limit remaining), every webhook delivery received (ID, type, signer, match), every deprecation warning emitted by the API (RFC 8594 <code>Deprecation<\/code> \/ <code>Sunset<\/code>), and every admin operation (create session, rotate secret, etc.). 30-day retention; pruned by a daily cron. Exportable to CSV for an external SIEM.<\/p><\/dd>\n<dt id=\"what%20about%20lgpd%20%2F%20gdpr%3F\"><h3>What about LGPD \/ GDPR?<\/h3><\/dt>\n<dd><p>The plugin registers handlers in <code>wp_privacy_personal_data_exporters<\/code> and <code>wp_privacy_personal_data_erasers<\/code>:<\/p>\n\n<ul>\n<li><strong>Exporter<\/strong> \u2014 returns every session associated with the data subject's email (name, emails, session ID, evidence ID, status, timestamps)<\/li>\n<li><strong>Eraser<\/strong> \u2014 redacts the signer's name and email to <code>[redacted-&lt;hash8&gt;]<\/code>, but <strong>preserves<\/strong> the evidence ID, transaction ID, session ID, and timestamps. Reason: electronic-signature law requires evidence retention for the legal retention period, even after a request to erase. Identity is redacted locally; the evidence package on the server stays intact for future legal audits.<\/li>\n<\/ul><\/dd>\n<dt id=\"does%20the%20plugin%20work%20without%20woocommerce%3F\"><h3>Does the plugin work without WooCommerce?<\/h3><\/dt>\n<dd><p>Yes. The WooCommerce integration is optional and only loads when WooCommerce is active. The shortcode, Gutenberg block, envelopes, Verify page, audit log, and WP-CLI all work independently.<\/p><\/dd>\n<dt id=\"can%20i%20test%20for%20free%3F\"><h3>Can I test for free?<\/h3><\/dt>\n<dd><p>Yes. Set the environment to \"HML (Sandbox)\" in the settings. Test data, simulated OTP (<code>000000<\/code> or <code>123456<\/code> are always accepted), mocked biometrics, no charges.<\/p><\/dd>\n<dt id=\"what%20is%20the%20maximum%20pdf%20size%3F\"><h3>What is the maximum PDF size?<\/h3><\/dt>\n<dd><p>The plugin accepts PDFs up to 15 MB. For larger files, increase <code>upload_max_filesize<\/code> and <code>memory_limit<\/code> in PHP and ensure your tenant is configured for large documents on SignDocs.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20any%20wordpress%20theme%3F\"><h3>Does it work with any WordPress theme?<\/h3><\/dt>\n<dd><p>Yes. The plugin uses minimal styles and respects your theme's CSS hierarchy. The button can be customized via a CSS class or via the brand color setting.<\/p><\/dd>\n<dt id=\"is%20signer%20data%20secure%3F\"><h3>Is signer data secure?<\/h3><\/dt>\n<dd><p>Yes. API credentials are encrypted in the database (AES-256-CBC with a key derived from <code>wp_salt<\/code>). OAuth tokens live in transients (never in permanent options). Webhook secrets are encrypted. The JWT private key (when used) is also encrypted. Webhook HMAC verification is constant-time (handled by the SDK). Webhook de-duplication via a transient lock prevents replay attacks.<\/p><\/dd>\n<dt id=\"can%20i%20customize%20the%20look%20of%20the%20signing%20page%3F\"><h3>Can I customize the look of the signing page?<\/h3><\/dt>\n<dd><p>Yes. Configure the brand color and logo in the plugin settings. The page hosted at <code>sign.signdocs.com.br<\/code> will display your visual identity. For deeper customization, contact support \u2014 corporate-level theming is available on the Enterprise plan.<\/p><\/dd>\n<dt id=\"does%20it%20work%20behind%20cloudfront%20%2F%20cloudflare%20%2F%20nginx%20proxy%3F\"><h3>Does it work behind CloudFront \/ Cloudflare \/ nginx proxy?<\/h3><\/dt>\n<dd><p>Yes. Set <code>signdocs_trusted_proxies<\/code> to a list of trusted CIDR ranges (e.g., <code>10.0.0.0\/8, 172.16.0.0\/12<\/code>). The plugin uses <code>X-Forwarded-For<\/code> only when <code>REMOTE_ADDR<\/code> is in a trusted range, preventing IP spoofing in the rate limiter and audit log.<\/p><\/dd>\n<dt id=\"is%20the%20plugin%20available%20in%20portuguese%3F\"><h3>Is the plugin available in Portuguese?<\/h3><\/dt>\n<dd><p>Yes. All user-facing strings are translatable (<code>signdocs-brasil<\/code> text domain) and the plugin ships with Brazilian Portuguese (<code>pt_BR<\/code>) and Spanish (<code>es_ES<\/code>) translations. WordPress automatically loads the right language pack based on your site's locale setting.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.3.7<\/h4>\n\n<p>WP.org reviewer feedback, round 2 (review ID <code>signdocs-brasil\/signdocsbrasil\/8May26\/T2 15May26\/4.0<\/code>).<\/p>\n\n<ul>\n<li><strong>New <code>== External services ==<\/code> section<\/strong> documenting every external endpoint the plugin contacts: the SignDocs API (<code>api.signdocs.com.br<\/code> \/ <code>api-hml.signdocs.com.br<\/code>), the OAuth2 token endpoint at the same base URL, the browser SDK CDN (<code>cdn.signdocs.com.br<\/code> \/ <code>cdn-hml.signdocs.com.br<\/code>), and the signing UI (<code>sign.signdocs.com.br<\/code>). Each subsection states what data is sent, when, and links to <a href=\"https:\/\/signdocs.com.br\/termos-de-uso\">Terms of Use<\/a> and <a href=\"https:\/\/signdocs.com.br\/politica-de-privacidade\">Privacy Policy<\/a>.<\/li>\n<li><strong>Trusted-proxy IP resolver hardened.<\/strong> <code>ClientIp::resolve()<\/code> previously returned <code>$_SERVER['REMOTE_ADDR']<\/code> verbatim when no trusted-proxy chain matched, propagating malformed values into the rate-limit transient key and audit log. Both return paths now gate on <code>filter_var(..., FILTER_VALIDATE_IP)<\/code>; invalid values become empty strings. The anonymous-signing AJAX rate limiter now routes through <code>ClientIp::resolve()<\/code> instead of reading <code>$_SERVER['REMOTE_ADDR']<\/code> directly.<\/li>\n<li><strong>Allow-list validation on user-supplied policy \/ locale.<\/strong> <code>Signdocs_Ajax::create_session<\/code> and <code>Signdocs_WooCommerce::save_product_meta<\/code> previously accepted any <code>sanitize_text_field<\/code>-cleaned string for the <code>policy<\/code> and <code>locale<\/code> fields, then forwarded it to the API. Both call sites now compare against the canonical lists (<code>Signdocs_Settings::get_policy_options()<\/code> for policy, <code>['pt-BR', 'en', 'es']<\/code> for locale) and fall back to the configured plugin default when the submitted value isn't recognized. Avoids 4xx round-trips against the SignDocs API for deprecated profile names.<\/li>\n<li><strong><code>is_email()<\/code> validation on signer email.<\/strong> <code>sanitize_email()<\/code> strips invalid characters but doesn't reject a value that fails to parse as an email. Added an <code>is_email()<\/code> check next to the existing required-field validation in <code>Signdocs_Ajax::create_session<\/code>.<\/li>\n<li><strong><code>wp_unslash()<\/code> on remaining admin reads.<\/strong> The Verify-page POST handler (<code>VerifyPage::render<\/code>) now unslashes <code>$_POST['kind']<\/code> before comparing, matching the WP coding standard the reviewer's AI expects.<\/li>\n<li><strong><code>esc_html<\/code> on exception messages reaching the frontend<\/strong> in four call sites: <code>Signdocs_Ajax::create_session<\/code> (AJAX error response), <code>Signdocs_Settings::ajax_test_connection<\/code> and <code>::ajax_register_webhook<\/code> (settings-page AJAX), and <code>Signdocs_WooCommerce::create_signing_for_order<\/code> (order note). Exception text from upstream APIs can contain quotes or angle brackets that would break a frontend handler that does <code>innerHTML = response.message<\/code>.<\/li>\n<\/ul>\n\n<h4>1.3.6<\/h4>\n\n<p>WP.org reviewer feedback, round 1 (received 2026-05-05, ~9h after submission).<\/p>\n\n<ul>\n<li><strong>Description rephrased.<\/strong> The short description previously read \"the most complete WordPress plugin for legally-binding electronic signatures in Brazil.\" That's a comparative marketing claim, which the WP.org Plugin Guidelines disallow (Guideline 11). Replaced with \"the official WordPress plugin for legally-binding electronic signatures in Brazil\" \u2014 factual (this is the first-party plugin published by SignDocs Brasil), non-comparative.<\/li>\n<li><strong>Webhook REST route now uses a proper <code>permission_callback<\/code>.<\/strong> Previously the route registered with <code>permission_callback =&gt; '__return_true'<\/code> and verified the HMAC signature inside the request handler. While that behaved correctly (signed-out requests were rejected), the WP REST introspection layer reported the endpoint as \"publicly accessible,\" which the reviewer flagged. New <code>Signdocs_Webhook::authorize()<\/code> performs HMAC verification at the permission-check phase and returns <code>WP_Error<\/code> with a precise HTTP status (401 for invalid signatures, 500 for server-side misconfiguration). The handler is now responsible only for body parsing and event dispatch. No change to the wire contract \u2014 third-party callers (the SignDocs Brasil API server) see identical request \/ response behavior.<\/li>\n<\/ul>\n\n<h4>1.3.5<\/h4>\n\n<p>WP.org submission auto-scanner fix.<\/p>\n\n<ul>\n<li><strong>Differentiated Plugin URI from Author URI.<\/strong> Both headers in <code>signdocs-brasil.php<\/code> were <code>https:\/\/signdocs.com.br<\/code>, which the WP.org submission auto-scanner rejects (the two URIs must be distinct or one omitted; per WP.org policy a Plugin URI is a page about this specific plugin and an Author URI is a page about the author). Plugin URI now points to the canonical GitHub repository (<code>https:\/\/github.com\/signdocsbrasil\/signdocs-brasil-wordpress<\/code>); Author URI remains the company site (<code>https:\/\/signdocs.com.br<\/code>). Once the plugin is approved on WP.org we may switch Plugin URI to the wordpress.org\/plugins\/signdocs-brasil\/ page.<\/li>\n<\/ul>\n\n<h4>1.3.4<\/h4>\n\n<p>Plugin Check (PCP) hardening pass for WP.org submission. No runtime behavior changes \u2014 every fix in this release is either annotation, defensive cleanup, or removal of a benign-but-noisy header.<\/p>\n\n<ul>\n<li><strong>Dropped <code>Domain Path: \/languages<\/code> header<\/strong> \u2014 the plugin doesn't ship <code>.pot<\/code> \/ <code>.mo<\/code> files yet (translations are loaded via WP.org's automatic language packs once approved), and the empty <code>languages\/<\/code> folder doesn't exist in the distribution zip. PCP rightly flagged the header as pointing to a non-existent path.<\/li>\n<li><strong><code>wp_unslash()<\/code> + explicit sanitization on every <code>$_POST<\/code> \/ <code>$_SERVER<\/code> read<\/strong> \u2014 added across <code>class-signdocs-ajax.php<\/code> and <code>class-signdocs-woocommerce.php<\/code>. The values were already being passed through <code>sanitize_text_field()<\/code> \/ <code>absint()<\/code> \/ <code>sanitize_email()<\/code> \/ <code>esc_url_raw()<\/code>, but <code>wp_unslash()<\/code> is the WPCS-canonical pattern and reviewers expect it.<\/li>\n<li><strong>Documented <code>phpcs:ignore<\/code> annotations on the custom-table queries.<\/strong> <code>AuditQuery<\/code>, <code>Logger<\/code>, <code>webhook<\/code> controller, and <code>uninstall.php<\/code> all touch <code>{$wpdb-&gt;prefix}signdocs_log<\/code> (or <code>$wpdb-&gt;postmeta<\/code> for the webhook lookup). PCP's <code>WordPress.DB.DirectDatabaseQuery.*<\/code> is unavoidable for plugins with their own tables \u2014 every annotation now states the table being touched and why core's caching\/query API doesn't apply.<\/li>\n<li><strong><code>AuditQuery<\/code> dynamic-prepare pattern documented inline.<\/strong> <code>WordPress.DB.PreparedSQL.InterpolatedNotPrepared<\/code> and <code>PreparedSQLPlaceholders.*<\/code> warnings on <code>AuditQuery::count()<\/code> \/ <code>select()<\/code> are PCP false positives \u2014 the <code>{$where}<\/code> fragment is built only from <code>Filters<\/code> allow-listed columns and the <code>{$orderBy}<\/code> \/ <code>{$order}<\/code> are validated against <code>ALLOWED_ORDER_COLUMNS<\/code> and <code>validatedOrder()<\/code>. Annotated explicitly so future readers (and reviewers) don't have to re-discover this.<\/li>\n<li><strong><code>wp_enqueue_script<\/code> for the CDN-hosted browser SDK now passes a version arg<\/strong> instead of <code>null<\/code> (<code>includes\/class-signdocs-shortcode.php:140<\/code>). The CDN already serves immutable <code>v1<\/code>-pinned bundles, but a version argument silences PCP's <code>EnqueuedResourceParameters.MissingVersion<\/code> and keeps the dev-tools network panel readable.<\/li>\n<\/ul>\n\n<p>Plugin Check status after this release: <strong>0 ERRORs, ~30 WARNINGs<\/strong> (down from 79; remaining warnings are documented false positives \u2014 webhook HMAC-vs-nonce, server-side hooks, plugin-specific custom-table queries, and the local-vs-global naming heuristic).<\/p>\n\n<h4>1.3.3<\/h4>\n\n<p>Cleanup pass after the v1.3.2 acceptance run.<\/p>\n\n<ul>\n<li><strong><code>wp signdocs webhook-test<\/code> actually works.<\/strong> The SDK's typed <code>WebhookTestResponse<\/code> model expects <code>{deliveryId, status, statusCode}<\/code> but the API returns <code>{webhookId, testDelivery: {httpStatus, success, timestamp}}<\/code>, so the typed call returned all-empty fields. CLI now bypasses the typed wrapper and reads the raw response, printing the real HTTP status + delivery timestamp. SDK fix tracked separately; the CLI unblocks operators today.<\/li>\n<li><strong>Dispatcher: dropped dead <code>SIGNING_SESSION.*<\/code> branches.<\/strong> The OpenAPI spec lists these but the server never emits them \u2014 the lifecycle is communicated entirely through the corresponding <code>TRANSACTION.*<\/code> events. Same cleanup applied to the legacy webhook controller in <code>includes\/<\/code>. No behavior change; just removes confusion for anyone reading the dispatch table.<\/li>\n<li><strong>Audit log writes on success, not just on warnings.<\/strong> A <code>webhook.completed<\/code> info row now lands in <code>signdocs_log<\/code> for every <code>TRANSACTION.COMPLETED<\/code>, capturing transaction ID, evidence ID, and the matched CPT post ID. Brings the table in line with the readme's \"every API call recorded\" claim.<\/li>\n<li><strong>WP-CLI <code>webhook-test<\/code> and <code>log-tail<\/code> now register with their dashed names<\/strong> as documented in the class header (previously the <code>_<\/code> form silently took precedence).<\/li>\n<\/ul>\n\n<h4>1.3.2<\/h4>\n\n<p>Two production-acceptance fixes uncovered while running the v1.3.1 release against real HML webhooks and the verify admin UI.<\/p>\n\n<ul>\n<li><strong>Webhook dedup keyed off the wrong identifier<\/strong> \u2014 <code>X-SignDocs-Webhook-Id<\/code> carries the <em>subscription<\/em> ID (<code>wh_*<\/code>), not a per-delivery ID. The previous dedup transient used that header as the key, so the first delivery for a subscription poisoned the cache for the full 7-day TTL and every subsequent webhook (including <code>TRANSACTION.COMPLETED<\/code>) returned 200\/deduped without ever reaching the dispatcher. CPT records stayed stuck in <code>PENDING<\/code>. Now keys off the body's top-level <code>id<\/code> (the actual delivery ID, <code>del_*<\/code>).<\/li>\n<li><strong>Custom capabilities resolved to <code>do_not_allow<\/code><\/strong> \u2014 the envelope CPT's <code>'capabilities'<\/code> map remapped <code>read_post<\/code>\/<code>edit_post<\/code> to <code>signdocs_verify<\/code>\/<code>signdocs_send<\/code>\/<code>signdocs_manage<\/code>, which registered them in WordPress's <code>$post_type_meta_caps<\/code> table as <em>meta<\/em> caps. Core's <code>map_meta_cap()<\/code> then short-circuited them to <code>do_not_allow<\/code> whenever called without a post argument \u2014 so even an administrator got HTTP 403 on the Verify admin page. Switched the envelope CPT to a custom <code>capability_type<\/code> and translate the generated CPT-cap names to the four primitive <code>signdocs_*<\/code> caps via <code>Capabilities::mapMetaCap<\/code>.<\/li>\n<li><strong>Verified end-to-end against HML<\/strong>: full create \u2192 sign \u2192 <code>TRANSACTION.COMPLETED<\/code> webhook \u2192 CPT updated to COMPLETED with <code>evidenceId<\/code> \u2192 <code>verification-&gt;verify($evidenceId)<\/code> returns the signed evidence record (CPF, policy, completion timestamp).<\/li>\n<\/ul>\n\n<h4>1.3.1<\/h4>\n\n<p>WP.org submission readiness \u2014 Plugin Check (PCP) baseline + canonical English readme + complete CPF\/CNPJ collection.<\/p>\n\n<ul>\n<li><strong>Plugin Check: 0 ERRORs<\/strong> \u2014 fixed 12 PCP error-level findings: 4\u00d7 missing <code>defined('ABSPATH')<\/code> guards in <code>src\/Admin\/{VerifyPage,AuditTable}.php<\/code>, <code>src\/Cpt\/EnvelopeCpt.php<\/code>, <code>src\/Webhook\/Controller.php<\/code>; 3\u00d7 missing <code>translators:<\/code> comments on <code>__()<\/code> calls with placeholders in WooCommerce integration; 2\u00d7 output escape gaps in WooCommerce email body; 1\u00d7 output escape gap on the CPT status badge (now via <code>wp_kses_post()<\/code>); <code>strip_tags()<\/code> \u2192 <code>wp_strip_all_tags()<\/code> in the unit-test fallback path of <code>Filters<\/code>; documented <code>fopen<\/code> \/ <code>fwrite<\/code> \/ <code>fclose<\/code> in <code>AuditExport<\/code> as a streaming-CSV pattern with file-scoped <code>phpcs:ignore<\/code>.<\/li>\n<li><strong>Canonical readme rewritten in English.<\/strong> WP.org policy 2025-07-28 requires the description, short description, and FAQ to be in English. The old Portuguese sections move to the standard i18n flow \u2014 <code>pt_BR<\/code> site visitors will see the localized strings via WordPress.org's automatic translation pack delivery once the plugin is approved.<\/li>\n<li><strong>Removed <code>load_plugin_textdomain()<\/code><\/strong> \u2014 discouraged since WordPress 4.6 for plugins on WP.org. WordPress core auto-loads the right language pack from the plugin slug.<\/li>\n<li><strong>CPF \/ CNPJ collection at every entry point<\/strong> \u2014 the SignDocs API requires <code>signer.cpf<\/code> or <code>signer.cnpj<\/code> at session-create time. Added to: shortcode form (when <code>show_form=\"true\"<\/code>), AJAX handler validation, frontend JS payload, <code>wp signdocs send --cpf=<\/code> \/ <code>--cnpj=<\/code> flags, WooCommerce integration (reads <code>_billing_cpf<\/code> \/ <code>_billing_cnpj<\/code> from order meta \u2014 works with the standard \"Brazilian Market on WooCommerce\" extension), envelope service per-signer.<\/li>\n<li><strong><code>wp signdocs send<\/code> outputs the full shareable URL<\/strong> \u2014 previously printed only the base session URL, which is not directly usable. Now appends <code>?cs=&lt;clientSecret&gt;<\/code> (URL-encoded) so the printed link can be opened directly to start signing.<\/li>\n<li><strong><code>wp signdocs status<\/code> now requires <code>--client-secret<\/code><\/strong> \u2014 documented the embed-token authentication contract. Full implementation deferred to a follow-up release.<\/li>\n<li><strong>Acceptance test against real HML<\/strong>: WP 6.9.x + MariaDB 11 in podman, plugin installed from the v1.3.1 zip, real HML credentials. Confirmed: <code>signingSessions-&gt;create<\/code> returns a valid <code>sessionId<\/code> + <code>clientSecret<\/code> + <code>url<\/code>; <code>envelopes-&gt;create<\/code> returns a valid <code>envelopeId<\/code> (PARALLEL, 2 signers); WP-CLI validates CPF\/CNPJ inputs correctly; capabilities install on activation; plugin co-active with WooCommerce 10.7.<\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<p>Alignment with PHP SDK 1.4.0 + complete English readme.<\/p>\n\n<ul>\n<li><strong>PHP SDK upgraded to <code>^1.4<\/code><\/strong> \u2014 SDK 1.4.0 fixed a model-shape divergence in <code>CreateSigningSessionRequest<\/code> \/ <code>CreateEnvelopeRequest<\/code> that had existed since 1.0.0: the correct field names accepted by the API are <code>purpose<\/code>, <code>policy<\/code>, <code>signer<\/code>, <code>document<\/code>, <code>returnUrl<\/code>, <code>cancelUrl<\/code>, <code>metadata<\/code>, <code>locale<\/code>, <code>expiresInMinutes<\/code>, <code>appearance<\/code>. Plugin call sites (CLI + AJAX) were updated.<\/li>\n<li><strong>CPF \/ CNPJ collection at every entry point<\/strong> \u2014 the API requires <code>signer.cpf<\/code> or <code>signer.cnpj<\/code> at session creation. The shortcode form now exposes CPF + CNPJ fields when <code>show_form=\"true\"<\/code>; the AJAX handler validates them; <code>wp signdocs send<\/code> accepts <code>--cpf<\/code> \/ <code>--cnpj<\/code>; the WooCommerce integration reads <code>_billing_cpf<\/code> \/ <code>_billing_cnpj<\/code> from the order (Brazilian Market on WooCommerce extension keys); the envelope service propagates per-signer CPF.<\/li>\n<li><strong>Readme rewritten in English<\/strong> \u2014 per <a href=\"https:\/\/make.wordpress.org\/plugins\/2025\/07\/28\/requiring-the-readme-to-be-written-in-english\/\">WP.org policy from 2025-07-28<\/a>, the canonical readme description must be in English. Portuguese localization is loaded automatically from the bundled <code>.po<\/code> \/ <code>.mo<\/code> translation files for <code>pt_BR<\/code> sites.<\/li>\n<li><strong>Plugin Check (PCP) errors fixed<\/strong> \u2014 direct file access guards (<code>defined('ABSPATH')<\/code>) added to 4 source files; missing <code>translators:<\/code> comments added to 3 <code>__()<\/code> calls with placeholders; output escaping tightened in WooCommerce email and CPT badge rendering; <code>strip_tags()<\/code> swapped for <code>wp_strip_all_tags()<\/code> in non-test paths.<\/li>\n<li><strong>\"Tested up to\" updated<\/strong> to WordPress 6.9 (the version used in the automated pen test).<\/li>\n<\/ul>\n\n<h4>1.2.3<\/h4>\n\n<p>Security sniff cleanup \u2014 zero <code>phpcs:ignore<\/code> comments in source.<\/p>\n\n<ul>\n<li><strong>Consolidated exceptions<\/strong> \u2014 the 7 remaining WPCS findings that can't be fixed through refactor (MySQL doesn't allow identifier placeholders; custom tables can't use <code>WP_Query<\/code>; admin audit logs shouldn't be cached; list-table pagination doesn't nonce in WP convention) are now declared as file-scoped exclusions in <code>phpcs.xml.dist<\/code> with written rationale for each. Zero line-level <code>phpcs:ignore<\/code> comments remain in <code>src\/<\/code>.<\/li>\n<li><strong><code>EventRouter::queryByMeta<\/code> refactored<\/strong> \u2014 dropped the direct <code>$wpdb-&gt;postmeta<\/code> lookup in favor of <code>get_posts(['meta_query' =&gt; \u2026, 'fields' =&gt; 'ids'])<\/code>. Slightly safer (adds post-type filter), eliminates two <code>DirectDatabaseQuery<\/code> warnings without suppression.<\/li>\n<li><strong><code>Filters::fromRequest<\/code> signature tightened<\/strong> \u2014 no longer falls back to <code>$_REQUEST<\/code> when called without arguments; callers must pass the request array explicitly. Moves the superglobal read-site up to the admin page, where CSRF\/capability context is clear.<\/li>\n<li><strong>Net result:<\/strong> zero security-category PHPCS findings, zero <code>phpcs:ignore<\/code> suppressions, and every exception documented in one auditable file.<\/li>\n<\/ul>\n\n<h4>1.2.2<\/h4>\n\n<p>Security audit + refactor.<\/p>\n\n<ul>\n<li><strong>AuditQuery refactor<\/strong> \u2014 all raw SQL extracted into <code>src\/Admin\/AuditQuery.php<\/code>. New <code>src\/Admin\/Filters.php<\/code> value object enforces validation at its constructor, so by the time <code>AuditQuery<\/code> sees a value it's already allow-list-checked. <code>AuditTable<\/code>, <code>AuditExport<\/code>, and <code>SigndocsCommand::log_tail<\/code> are now thin consumers with zero raw SQL.<\/li>\n<li><strong>SQL-injection fuzz test suite<\/strong> \u2014 <code>tests\/Unit\/AuditQueryFuzzTest.php<\/code> runs 24 SQL-injection payloads across every filter field (level, event_type, from, to, orderby, order) and asserts that every payload is either rejected by the allow-list or survives only via <code>%s<\/code> placeholders \u2014 never into a SQL literal. 47 total unit tests \/ 552 assertions, all green.<\/li>\n<li><strong>Black-box pen test<\/strong> \u2014 <code>tests\/pen_test.sh<\/code> exercises a running WordPress 6.9.x + MariaDB 11 stack (podman pod). Tests SQLi across audit filters + CSV export, webhook HMAC bypass attempts (no-sig, wrong-sig, stale-ts, garbage-ts, valid-sig, replay-dedup), CSRF on admin-post.php, and subscriber-role authorization against the audit log. All checks passed \u2014 runtime behavior matches the <code>phpcs:ignore<\/code> justifications.<\/li>\n<\/ul>\n\n<h4>1.2.1<\/h4>\n\n<p>WP.org submission-readiness pass.<\/p>\n\n<ul>\n<li>phpcbf auto-fixed ~3,300 cosmetic WPCS findings; remaining ~370 are pure style (snake_case \/ Yoda) and annotated as advisory in CI.<\/li>\n<li>Audited and annotated the 26 security-adjacent PHPCS findings (nonce verification, prepared SQL) \u2014 all verified as safe with justifying <code>phpcs:ignore<\/code> comments.<\/li>\n<li>Added <code>.wordpress-org\/<\/code> asset bundle: icon-256, icon-128, and auto-generated branded banner-1544x500 \/ banner-772x250 (designer should replace banners before public launch).<\/li>\n<li>Added <code>.github\/workflows\/wp-org-deploy.yml<\/code> \u2014 <code>10up\/action-wordpress-plugin-deploy<\/code> on tag push, gated by the <code>DEPLOY_TO_WPORG<\/code> repo variable so it's a no-op until WP.org approves.<\/li>\n<li>Added <code>DEPLOY.md<\/code> runbook for the WP.org submission and release flow.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<p>Enterprise feature parity with the external API.<\/p>\n\n<ul>\n<li><strong>Multi-signer envelopes<\/strong> \u2014 new <code>signdocs_envelope<\/code> CPT (parent of <code>signdocs_signing<\/code>), <code>EnvelopeService<\/code> wrapping the SDK's envelope resource, deterministic idempotency keys on create. Sequential and parallel signing flows.<\/li>\n<li><strong>Verification admin UI<\/strong> \u2014 \"Verificar\" submenu under SignDocs, accepts an evidence ID or envelope ID and renders signer identities, tenant CNPJ, consolidated downloads (<code>.p7s<\/code> or combined PDF). Uses SDK 1.3.0's <code>verifyEnvelope<\/code>.<\/li>\n<li><strong>Private Key JWT auth<\/strong> \u2014 alternative to <code>client_secret<\/code>. Store a PEM-encoded ES256 private key + key ID; ClientFactory branches on <code>signdocs_auth_method<\/code>. Preferred by regulated customers who can't store shared secrets at rest.<\/li>\n<li><strong>Audit log UI<\/strong> \u2014 <code>WP_List_Table<\/code> over <code>wp_signdocs_log<\/code> with filters (level, event type, date range) and CSV export, gated by <code>signdocs_view_logs<\/code>. CSV streams from <code>php:\/\/output<\/code> with chunked reads \u2014 safe for multi-GB exports.<\/li>\n<li><strong>Webhook secret rotation<\/strong> \u2014 <code>SecretResolver<\/code> accepts both the primary and a previous secret during a 7-day grace window. Daily cron <code>signdocs_expire_prev_secret<\/code> clears the previous secret once the window expires. Controller's authorize step tries each configured secret before rejecting.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<p>Hardening release + alignment with SignDocs PHP SDK 1.3.0.<\/p>\n\n<ul>\n<li><strong>Shared OAuth token cache<\/strong> \u2014 SDK <code>TokenCacheInterface<\/code> is implemented by <code>WpTransientTokenCache<\/code>, so a single token is reused across every PHP-FPM worker instead of one token fetch per request.<\/li>\n<li><strong>Webhook hardening<\/strong> \u2014 timestamp drift gate (\u2264300s), replay de-duplication via <code>X-SignDocs-Webhook-Id<\/code> transient lock (7-day TTL), proper <code>permission_callback<\/code> that runs HMAC before any business logic, input-shape guard on session \/ transaction IDs.<\/li>\n<li><strong>Full webhook coverage<\/strong> \u2014 added <code>TRANSACTION.CREATED<\/code>, <code>TRANSACTION.FALLBACK<\/code>, <code>STEP.STARTED\/COMPLETED\/FAILED<\/code>, <code>QUOTA.WARNING<\/code>, <code>API.DEPRECATION_NOTICE<\/code>, plus the two NT65 INSS-consignado events <code>STEP.PURPOSE_DISCLOSURE_SENT<\/code> and <code>TRANSACTION.DEADLINE_APPROACHING<\/code>. Covers the 13 events the server actually emits today.<\/li>\n<li><strong>Observability<\/strong> \u2014 <code>Deprecation<\/code> \/ <code>Sunset<\/code> (RFC 8594) response headers surface as admin notices; <code>RateLimit-*<\/code> headers are captured in a transient for the dashboard widget; structured log table <code>{prefix}signdocs_log<\/code> with 30-day retention and a daily cron prune.<\/li>\n<li><strong>Idempotency<\/strong> \u2014 <code>X-Idempotency-Key<\/code> is now sent on every resource-creating call, derived deterministically from site URL + user + action + resource, so AJAX retries no longer create duplicate sessions.<\/li>\n<li><strong>Capability model<\/strong> \u2014 four new caps (<code>signdocs_manage<\/code>, <code>signdocs_send<\/code>, <code>signdocs_verify<\/code>, <code>signdocs_view_logs<\/code>) instead of raw <code>manage_options<\/code> \/ <code>edit_posts<\/code>. Granted to administrator \/ editor \/ author on activation via <code>Capabilities::install()<\/code>; <code>map_meta_cap<\/code> wires them to CPT operations.<\/li>\n<li><strong>LGPD \/ GDPR<\/strong> \u2014 <code>wp_privacy_personal_data_exporters<\/code> and <code>wp_privacy_personal_data_erasers<\/code> are registered. The eraser redacts signer name + email but preserves evidence IDs and completion timestamps for legal retention.<\/li>\n<li><strong>WP-CLI<\/strong> \u2014 <code>wp signdocs health|send|status|webhook-test|log-tail<\/code>.<\/li>\n<li><strong>Test suite<\/strong> \u2014 PHPUnit (Brain Monkey unit tests), PHPStan level 5 (with <code>phpstan-wordpress<\/code>), PHPCS (WordPress-Extra + PHPCompatibilityWP), GitHub Actions matrix on PHP 8.1 \/ 8.2 \/ 8.3.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release<\/li>\n<li><code>[signdocs]<\/code> shortcode with 8 configurable attributes<\/li>\n<li>Gutenberg block with live preview (ServerSideRender)<\/li>\n<li><code>signdocs_signing<\/code> custom post type with status, signer, and policy columns<\/li>\n<li>REST webhook receiver with HMAC-SHA256 verification<\/li>\n<li>WooCommerce integration: product tab, automatic email, order notes<\/li>\n<li>AES-256-CBC encryption for stored credentials<\/li>\n<li>Popup, redirect, and overlay support<\/li>\n<li>Rate limiting for anonymous signing<\/li>\n<li>Trilingual: pt-BR, en, es<\/li>\n<\/ul>","raw_excerpt":"Legally-binding e-signature for Brazil: OTP, biometrics, ICP-Brasil, multi-signer envelopes, audit log, WP-CLI, WooCommerce.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/309417","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=309417"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/signdocsbrasil"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=309417"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=309417"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=309417"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=309417"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=309417"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=309417"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}