{"id":15,"date":"2026-10-05T20:11:38","date_gmt":"2026-10-06T00:11:38","guid":{"rendered":"https:\/\/demo.doctorx.ca\/esign\/for-developers\/"},"modified":"2026-10-05T20:11:38","modified_gmt":"2026-10-06T00:11:38","slug":"for-developers","status":"publish","type":"page","link":"https:\/\/demo.doctorx.ca\/esign\/for-developers\/","title":{"rendered":"For developers"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">What developers can build on: hooks, shortcodes, blocks, REST routes and WP-CLI commands, taken from each plugin&#8217;s own readme and from what this site has registered.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">DoctorX e-Sign<\/h2>\n\n\n\n<ul class=\"drx-dev-facts\"><li><strong>REST routes:<\/strong> <code>\/doctorx-esign\/v1<\/code>, <code>\/doctorx-esign\/v1\/envelopes\/(?P&lt;id&gt;&#92;d+)<\/code>, <code>\/doctorx-esign\/v1\/envelopes\/(?P&lt;id&gt;&#92;d+)\/draft<\/code>, <code>\/doctorx-esign\/v1\/envelopes\/(?P&lt;id&gt;&#92;d+)\/apply-layout<\/code>, <code>\/doctorx-esign\/v1\/envelopes\/(?P&lt;id&gt;&#92;d+)\/send<\/code>, <code>\/doctorx-esign\/v1\/portal\/open<\/code>, <code>\/doctorx-esign\/v1\/portal\/code<\/code>, <code>\/doctorx-esign\/v1\/portal\/verify<\/code>, <code>\/doctorx-esign\/v1\/portal\/document<\/code>, <code>\/doctorx-esign\/v1\/portal\/fields<\/code>, <code>\/doctorx-esign\/v1\/portal\/submit<\/code>, <code>\/doctorx-esign\/v1\/portal\/decline<\/code> and 2 more<\/li><\/ul>\n\n\n\n<p>Add-ons (for example an industry add-on with its own forms) can build on DoctorX e-Sign without changing it. Wait for the <code>doctorx_esign_loaded<\/code> action, then use:<\/p>\n<p>Placement templates (recognise an uploaded PDF and lay out its fields, coordinates only)<\/p>\n<ul>\n<li><code>doctorx_esign_register_placement_matcher( $id, $callback, $priority )<\/code> \u2014 your callback receives the document facts (<code>envelope_id<\/code>, <code>title<\/code>, <code>file_name<\/code>, <code>sha256<\/code>, <code>page_count<\/code>, <code>page_sizes<\/code>, <code>info<\/code> (PDF properties), <code>text<\/code> (callable: best-effort text of the first pages), <code>bytes<\/code> (callable)) and returns <code>null<\/code> or a layout: <code>id<\/code>, <code>label<\/code>, <code>roles<\/code> (<code>key<\/code>, <code>label<\/code>, <code>routing_order<\/code>), <code>fields<\/code> (<code>type<\/code>, <code>page<\/code> from 1, <code>x<\/code>, <code>y<\/code>, <code>w<\/code>, <code>h<\/code> as fractions of the displayed page from the top-left corner, <code>role<\/code>, <code>required<\/code>, <code>label<\/code>). The sender sees &quot;This document was recognised as \u2026&quot; with an Apply button.<\/li>\n<li>Filters <code>doctorx_esign_placement_matchers<\/code> and <code>doctorx_esign_placement_match<\/code> (final say over the recognised layout).<\/li>\n<li><code>doctorx_esign_apply_field_placements( $envelope_id, $layout, $args )<\/code> \u2014 apply a layout to a draft; signers are matched by role key and missing ones are created. Action <code>doctorx_esign_placements_applied<\/code>.<\/li>\n<li><code>doctorx_esign_replace_draft_pdf( $envelope_id, $bytes, $args )<\/code> \u2014 replace a draft&#039;s PDF (for example after filling in blanks or appending pages); signers and fields stay, fields on pages that no longer exist (or from <code>remove_fields_from_page<\/code> on) are removed, and the change is recorded in the audit trail. Action <code>doctorx_esign_draft_pdf_replaced<\/code>.<\/li>\n<\/ul>\n<p>Document templates, field types, identity checks<\/p>\n<ul>\n<li>Filter <code>doctorx_esign_document_templates<\/code> \u2014 documents offered on &quot;Send a document&quot; (<code>label<\/code>, <code>file<\/code> = path to a PDF you provide, optional <code>roles<\/code> and <code>fields<\/code>).<\/li>\n<li>Filter <code>doctorx_esign_field_types<\/code> \u2014 add field types (<code>label<\/code>, <code>input<\/code>, <code>kind<\/code> image\/text\/check, default <code>width<\/code>\/<code>height<\/code>, <code>required<\/code>, <code>auto<\/code>, <code>validate<\/code>).<\/li>\n<li>Filter <code>doctorx_esign_verification_methods<\/code> \u2014 add signer identity checks (<code>label<\/code>, <code>input<\/code>, <code>requires<\/code>, <code>available<\/code>, <code>send<\/code>, <code>verify<\/code>, <code>prompt<\/code> callbacks).<\/li>\n<\/ul>\n<p>Workflow<\/p>\n<ul>\n<li>Filter <code>doctorx_esign_pre_send_checks<\/code> \u2014 return extra messages to block sending, shown to the sender.<\/li>\n<li><code>doctorx_esign_log_event( $envelope_id, $event, $details, $signer_id )<\/code> \u2014 add an audit-trail entry (it also appears on the certificate). Filter <code>doctorx_esign_audit_event_labels<\/code> for labels; action <code>doctorx_esign_audit_event_logged<\/code>.<\/li>\n<li>Filter <code>doctorx_esign_certificate_sections<\/code> \u2014 change the completion certificate content.<\/li>\n<li>Actions <code>doctorx_esign_envelope_created<\/code>, <code>_sent<\/code>, <code>_signer_signed<\/code>, <code>_completed<\/code>, <code>_declined<\/code>, <code>_voided<\/code>, <code>_expired<\/code>, <code>_deleted<\/code>.<\/li>\n<li><code>doctorx_esign_create_envelope()<\/code>, <code>doctorx_esign_get_envelope()<\/code>, <code>doctorx_esign_get_meta()<\/code> \/ <code>doctorx_esign_update_meta()<\/code> (your own data on a document), <code>doctorx_esign_completed_pdf()<\/code>, <code>doctorx_esign_completion_certificate_pdf()<\/code> (the separate certificate, when the document&#039;s owner did not allow adding pages).<\/li>\n<li>Action <code>doctorx_esign_privacy_erased<\/code> \u2014 after a personal data erasure request was handled.<\/li>\n<\/ul>\n<p>Protected PDFs<\/p>\n<ul>\n<li><code>DoctorX_ESign_PDF_Reader::permissions()<\/code> says what the owner allows (<code>modify<\/code>, <code>annotate<\/code>, <code>fill<\/code>, <code>assemble<\/code>); <code>DoctorX_ESign_PDF_Writer::can()<\/code> asks the same. In a protected file <code>overlay_page()<\/code> needs <code>modify<\/code> and <code>append_page()<\/code> needs <code>assemble<\/code>; they throw a <code>DoctorX_ESign_PDF_Exception<\/code> with a readable message otherwise.<\/li>\n<li><code>DoctorX_ESign_PDF_Builder::place_values( $writer, $fields )<\/code> adds values (text, check marks, images) the way the permissions allow: drawn onto the page, as annotations, or as read-only form fields. Use it to write into a draft that may be protected.<\/li>\n<\/ul>\n<p>Admin<\/p>\n<ul>\n<li>Filter <code>doctorx_esign_settings_tabs<\/code> \u2014 add a settings tab (<code>label<\/code>, <code>render<\/code>, <code>save<\/code>; nonce and capability are checked for you).<\/li>\n<li>Action <code>doctorx_esign_admin_menu<\/code> \u2014 add pages under the e-Sign menu. Action <code>doctorx_esign_envelope_details<\/code> \u2014 add a panel to a document&#039;s details screen. Action <code>doctorx_esign_editor_panels<\/code> \u2014 add a panel above the field editor of a draft.<\/li>\n<li>Other filters: <code>doctorx_esign_email<\/code>, <code>doctorx_esign_user_can_access<\/code>, <code>doctorx_esign_client_ip<\/code>, <code>doctorx_esign_signature_subfilter<\/code>, <code>doctorx_esign_tsa_request_args<\/code>, <code>doctorx_esign_portal_csp<\/code>, <code>doctorx_esign_storage_base_dir<\/code>, <code>doctorx_esign_normalize_phone<\/code>.<\/li>\n<\/ul>\n<p>Capabilities: <code>drx_esign_send<\/code>, <code>drx_esign_view_all<\/code>, <code>drx_esign_manage<\/code>.<\/p>\n<p>Self-tests that run with plain PHP (no WordPress): <code>php tests\/selftest-signing.php<\/code>, <code>php tests\/selftest-encryption.php<\/code>, <code>php tests\/selftest-storage.php<\/code> and <code>php tests\/selftest-workflow.php<\/code>.<\/p>\n<h3>Third-party code<\/h3>\n<ul>\n<li>PDF.js 3.11.174 by Mozilla, Apache License 2.0 (assets\/vendor\/pdfjs\/LICENSE), bundled; used to show PDFs in the editor and on the signing page.<\/li>\n<li>Dancing Script, Alex Brush, Great Vibes and Sacramento fonts, SIL Open Font License 1.1 (assets\/fonts\/OFL-*.txt), for typed signatures.<\/li>\n<li>Character widths of the standard Helvetica fonts from the Adobe Core 14 font metrics.<\/li>\n<\/ul>\n\n\n","protected":false},"excerpt":{"rendered":"<p>What developers can build on: hooks, shortcodes, blocks, REST routes and WP-CLI commands, taken from each plugin&#8217;s own readme and from what this site has registered. DoctorX e-Sign REST routes: \/doctorx-esign\/v1, \/doctorx-esign\/v1\/envelopes\/(?P&lt;id&gt;&#92;d+), \/doctorx-esign\/v1\/envelopes\/(?P&lt;id&gt;&#92;d+)\/draft, \/doctorx-esign\/v1\/envelopes\/(?P&lt;id&gt;&#92;d+)\/apply-layout, \/doctorx-esign\/v1\/envelopes\/(?P&lt;id&gt;&#92;d+)\/send, \/doctorx-esign\/v1\/portal\/open, \/doctorx-esign\/v1\/portal\/code, \/doctorx-esign\/v1\/portal\/verify, \/doctorx-esign\/v1\/portal\/document, \/doctorx-esign\/v1\/portal\/fields, \/doctorx-esign\/v1\/portal\/submit, \/doctorx-esign\/v1\/portal\/decline and 2 more Add-ons (for example an industry add-on with its own forms) [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-15","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/demo.doctorx.ca\/esign\/wp-json\/wp\/v2\/pages\/15","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demo.doctorx.ca\/esign\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/demo.doctorx.ca\/esign\/wp-json\/wp\/v2\/types\/page"}],"replies":[{"embeddable":true,"href":"https:\/\/demo.doctorx.ca\/esign\/wp-json\/wp\/v2\/comments?post=15"}],"version-history":[{"count":2,"href":"https:\/\/demo.doctorx.ca\/esign\/wp-json\/wp\/v2\/pages\/15\/revisions"}],"predecessor-version":[{"id":18,"href":"https:\/\/demo.doctorx.ca\/esign\/wp-json\/wp\/v2\/pages\/15\/revisions\/18"}],"wp:attachment":[{"href":"https:\/\/demo.doctorx.ca\/esign\/wp-json\/wp\/v2\/media?parent=15"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}