Skip to content
Guide

Electronic Signature

Connect an App-owned business record to immutable documents, signature requests, and terminal outcomes without crossing the Core boundary.

Create a signed document for an App record using the Signature SDK. Prepare a protected Resource and Handle events and jobs first.

Implement the document-to-request flow

  1. Publish the template binding and its subject Resource resolver, using the registration example below. Reauthorize every exact source reference; a selected Party or Resource id is not read authority.
  2. Follow Electronic Signature contracts to call SignatureDocumentHost::createCurrentBound() outside the App transaction. Persist the returned identity and await signature.document.outcome.v1.
  3. Re-read the ready document and submit through SignatureHost with its exact public id, revision, and SHA-256 checksum, frozen participants, consent, expiry, and a durable idempotency key. Acceptance of document creation does not mean rendering is complete.
  4. Consume terminal outcomes once, matching subject and request identity and restoring current authority before changing the App record. Keep PDF bytes, credentials, invitation addresses, and signing evidence out of App handoff logs.

Confirm the result

The document becomes ready before a request is submitted; retries retain one accepted identity and duplicate events do not repeat the business effect. Revoked authority and mismatched revisions must be refused. packages/people/tests/Feature/EmploymentContractSignatureBridgeTest.php covers this durable bridge. Use Test an App for execution.

Register App bindings and data Resources

Register the template binding

A binding does not register its business Resource. People publishes people.employment_contract, a same-App Resource Reference resolver, and then PeopleSignatureDescriptors under a manifest-declared contribution location. The class implements AppDescriptorContribution; Core discovers its SignatureTemplateBindingDescriptor values from AppDescriptorSet.

PiecePeople exampleRegistration provides
Discovery rootPeopleCoreAppManifest::contributionLocations() includes src/ContributionDiscovers the contributor
Business ResourceContribution/Resources/EmploymentContractModule.phpEstablishes people.employment_contract identity and permission
Subject resolutionPeopleEmploymentContractReferenceResolver.phpReauthorizes the exact subject for actor and Legal Entity
Signature familyContribution/PeopleSignatureDescriptors.phpRegisters the binding in the Signature catalog
App data ResourcesPeopleSignatureDocumentDataSources.phpOffers Resource-backed Add App data sources

This focused, constructor-valid form uses the identifiers and contract fields from the real People declaration:

Code example
PHP
<?php

namespace Amuzcorp\Nexia\PeopleCore\Contribution;

use Nexia\AppDescriptors\Contracts\AppDescriptorContribution;
use Nexia\AppDescriptors\AppDescriptorSet;
use Nexia\AppDescriptors\DescriptorStatus;
use Nexia\AppDescriptors\SignatureSignatoryRoleDescriptor;
use Nexia\AppDescriptors\SignatureTemplateBindingDescriptor;
use Nexia\AppDescriptors\SignatureTemplateVariableDescriptor;
use Nexia\Signature\SignatureDataClassification;
use Nexia\Signature\SignatureVariableType;

final class PeopleSignatureDescriptors implements AppDescriptorContribution
{
    public static function appDescriptors(): AppDescriptorSet
    {
        return AppDescriptorSet::of(new SignatureTemplateBindingDescriptor(
            appKey: 'people',
            bindingKey: 'people.employment_contract',
            resourceKey: 'people.employment_contract',
            labelKey: 'people.signature_bindings.employment_contract.label',
            descriptionKey: 'people.signature_bindings.employment_contract.description',
            createPermissionKey: 'people.employment_contract.create_document',
            variables: [
                new SignatureTemplateVariableDescriptor('worker.full_name', 'people.signature_bindings.employment_contract.variables.worker_full_name', SignatureVariableType::String, true, SignatureDataClassification::Restricted),
                new SignatureTemplateVariableDescriptor('contract.starts_on', 'people.signature_bindings.employment_contract.variables.contract_starts_on', SignatureVariableType::Date, true, SignatureDataClassification::Internal),
            ],
            signatoryRoles: [
                new SignatureSignatoryRoleDescriptor('worker', 'people.signature_bindings.employment_contract.roles.worker', 1, 1),
            ],
            syntheticSample: [
                'worker.full_name' => 'Alex Kim',
                'contract.starts_on' => '2026-09-01',
            ],
            templateKeys: ['people.employment_contract.new_hire'],
            version: '1.0',
            status: DescriptorStatus::Active,
        ));
    }
}

Keep App keys in the owning namespace, match resourceKey to submitted subject references, and freeze published versions. Add every label key to the App locale catalog; keep synthetic samples free of real personal data.

After changing a contribution, refresh the generated runtime caches and inspect both discovery and descriptor validation:

Code example
Shell
task artisan -- nexia-runtime:refresh-runtime-caches --if-route-cached --no-ansi
task artisan -- nexia-apps:doctor-package-app people --no-ansi
task artisan -- nexia-apps:validate-app-descriptors people --no-ansi

Availability also requires an operational App, Active descriptor, createPermissionKey, and eligible published template.

Register App data Resources

Implement SignatureDocumentDataSourceContribution: appDescriptors() and signatureDocumentDataSourceProviders() must publish matching App/source/version pairs or Core fails closed. A model-backed source uses its canonical same-App Resource with a non-blank labelKey; supportedSubjectResourceKeys limits subjects but does not register them. Providers reauthorize every requested record, field, and relationship.

People's PeopleSignatureDocumentDataSources publishes these pairs:

Source identityDisplay ResourceLookupAccepted document subject
people.worker_employment@1people.employment_contractdirect_subject, onedirectory.party, people.employment_contract
people.worker@1people.workerderived_ref from worker, onepeople.employment_contract
people.employment@1people.employmentderived_ref from employment, onepeople.employment_contract
people.worker_assignments@1people.worker_assignmentderived_ref from worker, many ordered by effective_frompeople.employment_contract

Once People is operational, Core exposes those pairs without importing a People model or inferring fields.

Source of truth: docs/developers/content/en/platform-extensions/electronic-signature.md