Skip to content

feat: expose signing lifecycle and delivery outcomes via native webhooks - #9427

Merged
lfals merged 23 commits into
mainfrom
feat/native-lifecycle-webhook-events
Nov 13, 2026
Merged

lfals merged 23 commits into
mainfrom
feat/native-lifecycle-webhook-events

Conversation

@vitormattos

@vitormattos vitormattos commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Expose LibreSign's signing lifecycle and notification-submission outcomes as native Nextcloud Webhook Listeners events. Integrations can subscribe through Nextcloud's standard event infrastructure rather than a LibreSign-specific callback transport.

This targets LibreSign development main / Nextcloud 36. It does not change stable LibreSign 13–15 or introduce a new webhook HTTP endpoint. The existing SigningRequestCompletedEvent is retained.

Events

All new event classes implement IWebhookCompatibleEvent under OCA\Libresign\Events, with contractVersion: 1 and an eventId for idempotent consumers.

Event When emitted Correlation fields
SigningRequestReadyEvent A root document or envelope enters ABLE_TO_SIGN, including a newly created ready request Request UUID and type, status
SignerSignedWebhookEvent A signer completes a persisted document-level signature Root request UUID/type, document UUID, signer ID
SignerRejectedWebhookEvent A signer rejection is committed Root request UUID/type, document UUID, signer ID, workflow-canceled flag
SigningRequestCanceledEvent A root request is canceled by a committed rejection Root request UUID/type, status
NotificationDeliveryStatusChangedEvent A notification submission outcome is persisted Root request UUID/type, delivery ID, channel, state, attempt

The existing SigningRequestCompletedEvent continues to represent aggregate completion. A rejection under a continue policy can also complete a partially signed request.

Behavior and implementation

  • After-commit publication: business-state changes, signer association and status transitions commit before lifecycle events are dispatched. Request readiness is persisted atomically with the signers; a failed transaction can be retried without losing the draft-to-ready transition.
  • Envelope semantics: root-level readiness/cancellation/completion events identify the envelope; signer events identify the affected child document. Explicitly starting a new envelope in ABLE_TO_SIGN updates its children before publishing the root ready event. Ordinary envelope creation remains in draft.
  • Event identity: identifiers are stable for the same persisted business occurrence. Each distinct draft-to-ready transition receives its own persisted discriminator, so a request that returns to draft and becomes ready again yields a new eventId.
  • Failure isolation: NativeWebhookEventPublisher separates event preparation from listener dispatch. Failures raised by external listeners are logged without reversing completed business operations. Unexpected event-preparation errors remain visible rather than being silently suppressed.
  • Signature rejection architecture: validation, transactional persistence, workflow transitions and post-commit notifications have separate collaborators. Legacy rejection-listener failures are isolated per dispatch so they do not prevent notifications to subsequently activated signers. Ambiguous signer identification fails before persistence.
  • Diagnostics: errors are logged with type and source location but without exception messages containing potentially sensitive information.

Security and integration contract

Webhook payloads contain correlation identifiers and status information only. They do not contain recipient addresses, phone numbers, invitation/signing URLs or tokens, private rejection comments, document bytes, certificate material or signing credentials.

The existing internal SignRequestCanceledEvent concerns signer-removal notifications; it is not the new aggregate SigningRequestCanceledEvent.

A notification result describes the submission state recorded by the delivery ledger. In particular, accepted does not prove that the recipient received or read a message. Consumers must handle duplicate events and use authenticated LibreSign APIs to reconcile state or retrieve documents.

Validation

Tested locally with Nextcloud 36, PHP 8.3 and isolated synthetic SQLite data in a network-disabled Docker runtime:

  • 450 focused unit tests / 1,067 assertions and 15 integration tests / 52 assertions passed. The focused suite covers metadata normalization, public file responses, state transitions and signing/rejection workflows.
  • The full unit suite ran 4,859 tests / 14,503 assertions; its only error was an existing TSA test attempting to reach freetsa.org while network access was disabled.
  • Three additional regression tests verify that the persisted readiness event discriminator is excluded from validation and file-list API metadata while remaining available internally.
  • PHP syntax checks, project PHP-CS-Fixer and git diff --check passed.
  • Full Psalm analysis previously reported two pre-existing InvalidArgument errors in unchanged lib/Service/Install/SignSetupService.php; no new static-analysis errors have been confirmed.

Regression coverage includes root/child event payloads and privacy, duplicate event IDs across separate ready transitions, envelope readiness, failed signer writes and retries, transaction rollback, listener TypeError isolation, rejection rules and notification outcomes.

Delivery limits and compatibility

  • Delivery uses Nextcloud Webhook Listeners, not a custom LibreSign callback mechanism.
  • Event dispatch is best effort. This PR does not add a durable lifecycle-event outbox or guarantee delivery/replay after failed event enqueue.
  • The local tests validate event contracts and dispatch boundaries; they do not constitute an authenticated HTTP end-to-end test of Nextcloud Webhook Listeners. That integration path should be validated before merge.
  • No changes to the public signing API or to stable LibreSign 13–15 are intended.

Related: #9426 (separate removal of the legacy callback mechanism).

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Code Metrics Report

Note: Metrics describe the revision analyzed by the successful PHPUnit run. Newer commits with pending, canceled or failed checks may not yet be represented in this report.

main (b02d0b8) #9427 (59669a1) +/-
Coverage 76.1% 76.4% +0.2%
Test Execution Time 1s 3m44s +3m43s
Details
  |                     | main (b02d0b8) | #9427 (59669a1) |  +/-   |
  |---------------------|----------------|-----------------|--------|
+ | Coverage            |          76.1% |           76.4% |  +0.2% |
  |   Files             |            500 |             512 |    +12 |
  |   Lines             |          28578 |           28842 |   +264 |
+ |   Covered           |          21776 |           22055 |   +279 |
- | Test Execution Time |             1s |           3m44s | +3m43s |

Code coverage of files in pull request scope (80.7% → 83.0%, patch 96.4%)

Files Coverage +/- Patch Coverage Status
apps/libresign/lib/Events/SignatureRejectedEvent.php 40.0% -40.0% - affected
lib/Controller/SignFileController.php 81.5% +8.8% 100.0% modified
lib/Db/FileMapper.php 67.3% +5.9% 100.0% modified
lib/Events/NotificationDeliveryStatusChangedEvent.php 100.0% +100.0% 100.0% added
lib/Events/SignerRejectedWebhookEvent.php 100.0% +100.0% 100.0% added
lib/Events/SignerSignedWebhookEvent.php 100.0% +100.0% 100.0% added
lib/Events/SigningRequestCanceledEvent.php 100.0% +100.0% 100.0% added
lib/Events/SigningRequestReadyEvent.php 100.0% +100.0% 100.0% added
lib/Service/File/FileListService.php 83.0% +0.0% 100.0% modified
lib/Service/File/ValidationMetadataNormalizer.php 97.4% 0.0% 100.0% modified
lib/Service/FileStatusService.php 98.9% +1.1% 100.0% modified
lib/Service/Notification/NotificationSubmissionService.php 95.7% +0.0% 100.0% modified
lib/Service/RequestSignatureService.php 79.8% +1.0% 81.5% modified
lib/Service/SignFileService.php 79.5% -0.2% 100.0% modified
lib/Service/SignatureRejection/CommittedRejection.php 100.0% +100.0% 100.0% added
lib/Service/SignatureRejection/RejectionInput.php 100.0% +100.0% 100.0% added
lib/Service/SignatureRejection/SignatureRejectionNotifier.php 100.0% +100.0% 100.0% added
lib/Service/SignatureRejection/SignatureRejectionPersistence.php 100.0% +100.0% 100.0% added
lib/Service/SignatureRejection/SignatureRejectionRules.php 96.9% +96.9% 96.9% added
lib/Service/SignatureRejection/SignatureRejectionService.php 100.0% 0.0% 100.0% modified
lib/Service/SignatureRejection/SignatureRejectionWorkflow.php 98.4% +98.4% 98.4% added
lib/Service/Webhook/NativeWebhookEventPublisher.php 100.0% +100.0% 100.0% added

Reported by octocov

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos
vitormattos marked this pull request as ready for review November 10, 2026 01:46
@vitormattos
vitormattos requested a review from a team as a code owner November 10, 2026 01:46
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
…t failures

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
… tests

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
…t state

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>

@lfals lfals left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core change looks correct to me: the native events are published after the business operation commits, the payloads carry only the documented correlation fields, and failures of native listeners are isolated from the persisted workflow.

Comments below are one regression in the legacy rejection path, three gaps in the ready event (envelopes, a failed signer transaction, and a repeated eventId), one behaviour question, and one diagnosability suggestion.

Comment thread lib/Service/RequestSignatureService.php Resolved
Comment thread lib/Service/RequestSignatureService.php Resolved
Comment thread lib/Events/SigningRequestReadyEvent.php Resolved
…ations

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos
vitormattos requested a review from lfals November 11, 2026 04:38

@lfals lfals left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up on the latest commits. Most earlier comments are resolved: legacy listener failures no longer escape a committed rejection, the envelope ready event is reachable and tested, the status upgrade shares the signer transaction so a retry still publishes, ready_event_id gives each transition a distinct eventId without leaking into public responses, and the narrowed envelope lookup fails before any write, as documented by the new tests.

Comments below are the logging change that is only partly applied, and one side effect of moving persistFile() into the transaction.

Comment thread lib/Service/RequestSignatureService.php Resolved
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos
vitormattos requested a review from lfals November 11, 2026 22:52
@lfals
lfals merged commit a5740c4 into main Nov 13, 2026
80 checks passed
@lfals
lfals deleted the feat/native-lifecycle-webhook-events branch November 13, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants