Ga naar hoofdinhoud

Digital Signing Integration

Filinq provides a complete digital signing workflow — create signing requests, collect signatures from multiple signers, verify document integrity, and maintain an immutable audit trail. Signing can be performed natively within Nextcloud or delegated to an external provider such as ValidSign.

Overview​

The signing feature supports:

  • Signing requests — Create a request that routes a document to one or more signers
  • Signer actions — Sign or decline; bulk signing for multiple requests
  • Cancellation — Cancel an open request at any time
  • Verification — Verify the signature integrity of a signed document
  • Audit trail — Immutable log of all signing events per request
  • Pluggable providers — Native (local) signing or ValidSign external service

API Endpoints​

Create Signing Request​

POST /apps/filinq/api/signing/requests

Request body (JSON):

FieldTypeRequiredDescription
fileIdintYesNextcloud file ID of the document to sign
signersarrayYesList of signer objects { userId, email, name, level }
titlestringHuman-readable title for the request
messagestringMessage shown to signers
dueDatestringISO 8601 due date for completion
providerstringSigning provider: native (default) or validsign

Response:

{
"id": "request-uuid",
"status": "pending",
"fileId": 42,
"signers": [ { "id": "signer-uuid", "userId": "john", "status": "pending" } ],
"createdAt": "2025-01-15T10:00:00Z"
}

List Signing Requests​

GET /apps/filinq/api/signing/requests

Returns all signing requests visible to the current user.


Get Signing Request​

GET /apps/filinq/api/signing/requests/{id}

Cancel Signing Request​

DELETE /apps/filinq/api/signing/requests/{id}

Cancels an open request. Any pending signers are notified.


Sign Document​

POST /apps/filinq/api/signing/requests/{id}/sign

Records a signature for the current user on the given request.

Request body (JSON):

FieldTypeRequiredDescription
signerIdstringYesUUID of the signer entry within the request
pinstringPIN for native signing (if required)

Decline Signing​

POST /apps/filinq/api/signing/requests/{id}/decline

Request body:

FieldTypeRequiredDescription
signerIdstringYesUUID of the signer
reasonstringYesReason for declining

Bulk Sign​

POST /apps/filinq/api/signing/bulk

Sign multiple requests in a single call.

Request body:

{ "requestIds": ["uuid-1", "uuid-2"] }

Verify Document​

GET /apps/filinq/api/signing/verify/{fileId}

Verifies signature integrity of a signed document file.

Response:

{
"valid": true,
"signers": [
{ "name": "John Doe", "signedAt": "2025-01-16T09:00:00Z", "level": "AES" }
],
"verifiedAt": "2025-01-16T10:00:00Z"
}

Get Audit Trail​

GET /apps/filinq/api/signing/requests/{id}/audit

Returns the full audit trail for a signing request.

Response:

[
{ "event": "created", "userId": "admin", "timestamp": "2025-01-15T10:00:00Z" },
{ "event": "signed", "userId": "john", "timestamp": "2025-01-16T09:00:00Z" }
]

Signing Providers​

Filinq uses a provider abstraction (SigningProviderInterface) to support multiple backend signing implementations.

Native Provider (native)​

Signs documents within Nextcloud using a local key pair. No external service is required. Suitable for internal workflows and Basic/AES signatures.

ValidSign Provider (validsign)​

Delegates signing to the ValidSign external service. Suitable for QES/AES signatures with legal weight under eIDAS.

Required configuration:

Config keyDescription
filinq_validsign_api_urlValidSign API base URL
filinq_validsign_api_keyValidSign API key
filinq_validsign_sender_emailSender email for invitation notifications

Set via Filinq admin settings or:

docker exec nextcloud php occ config:app:set filinq filinq_validsign_api_key --value="your-key"

Signature Levels​

LevelNameDescription
BESBasic Electronic SignatureSimple click-to-sign, no certificate required
AESAdvanced Electronic SignatureIdentity-linked, supports local key pairs
QESQualified Electronic SignatureLegally equivalent to handwritten (eIDAS Art. 25)

A pupil of 14 cannot sign an ontwikkelingsperspectief (OPP) on their own. Filinq refuses the signature until a parent or guardian stands beside them on the same request.

The age. Set it under Settings > Digital signing > Guardian consent age. The default is 16, the age of consent in Dutch law (UAVG article 5). A request can raise it for its own signers: send guardianConsentAge: 18 on a praktijkovereenkomst (POK), because a student under 18 is a minor under civil law. A request can never lower it.

Naming the guardian. Give each signer entry the extra fields below. The app that asks for the signature knows the pupil's birth date and the parents, so it sends them.

FieldOnMeaning
birthDatethe pupilYYYY-MM-DD. Decides whether a guardian must sign too.
rolethe guardianguardian. Every other entry is a signer.
guardianForthe guardianThe userId or email of the pupil's entry in the same list.
guardianActthe guardianco-sign (default): signs the document too. consent: agrees that the pupil signs.
consentStatementa consent actThe text the guardian agrees to. Required for consent.
guardianRefthe guardianYour own reference to the guardian, for example a learniq guardian id.

What Filinq checks.

  • A request that names a pupil under the age without a guardian is refused (400).
  • The pupil's signature is refused (403) while no guardian on the request points at them.
  • A guardian signs through the same sign action as anyone else, in Nextcloud or in the portal.
  • A guardian cannot be the pupil, and cannot be under the age themselves.
  • The age is checked at the moment the pupil signs, not when the request was made.
  • The request completes only once a guardian has acted for every pupil under the age.

What the signed document records. The completed request and the signed file carry a consentBasis: per pupil, the signer record, the age that applied, the moment it was checked, and each guardian who acted, how, and how their identity was established. It never carries the birth date. In the signed file the basis sits inside the signature seal, so changing it afterwards makes verification fail.

Who uses it. learniq for the OPP and the POK, and portaliq for toestemmingsformulieren, once they raise their signing requests through DocumentSigningRequestedEvent.

Next: set the guardian consent age for your school in the signing settings.


Signer identity: DigiD, eHerkenning and iDIN​

A Nextcloud login tells Filinq which account is signing. It does not say how strongly that person proved who they are. For a signature that needs more, a signer confirms their identity once more through an identity broker, just before they sign.

Assurance levels. Filinq uses the three eIDAS levels: low, substantial and high. Each signature level has a floor, and a request can ask for more but never less.

Signature levelAssurance floor
SESlow
AdESsubstantial
QEShigh

A QES request that asks for low is stored at high, and the create response says so in assuranceFloor. A request made before this feature reads as low, so nothing changes for it.

What reaches which level.

Identity meansAssurance
Nextcloud loginlow
DigiD Midden or Substantieelsubstantial
DigiD Hooghigh
eHerkenning EH3substantial
eHerkenning EH4high
iDIN (your broker's value)substantial
Portal signerthe portal's verified trust

An unknown broker value counts as low, never higher.

Connect a broker. Under Settings > Signer identity:

  1. Create the broker's client secret in the OpenRegister credential broker, on an inject-only provider, and allow the app filinq. Copy the credential's ID.
  2. Fill in the issuer, the client ID, the authorization and token endpoints (https only) and the redirect URI https://<your-cloud>/index.php/apps/filinq/api/signing/identity/callback.
  3. Paste the credential ID into Credential reference. Filinq stores this reference, never the secret, and refuses anything that is not a credential ID.
  4. Add your broker's own iDIN value under Extra acr mapping. DigiD and eHerkenning are mapped already.
  5. Choose Identity broker as the identity provider and save.

Signing with a stronger identity. When a request asks for more than the signer has, the sign action answers 403 with a stepUp hint: the level needed and why. In the signing folder the signer then chooses Confirm my identity, logs in at the broker, comes back on the request and signs. A login stays valid for signing for 15 minutes by default; set another window in the settings. It counts for that one request and signer only.

The signing folder after a pass: the document asks for a stronger identity check

The dialog that sends the signer to the identity broker

Parents and guardians. A request can ask more of a guardian than of the pupil: send guardianRequiredAssurance: substantial and the parent confirms with DigiD while the pupil signs with their Nextcloud login. An administrator can set a minimum for every guardian under Minimum assurance for a parent or guardian.

The signer identity settings with the guardian minimum set to substantial

What is recorded. Each signer record, its audit entry and the signed file carry the identity evidence: provider, means, level, a pseudonym, the moment and a hash of the broker's token. Filinq never keeps a BSN or the token itself; the subject is always hashed with a key unique to your Nextcloud. The completed request carries the weakest level among its signers as resolvedAssurance, and apps that asked for the signature receive that same value.

EUDI wallet. Every EU country must offer its citizens an EUDI wallet by December 2026, with qualified signatures from a phone. Filinq does not ship a wallet integration today. The identity seam is built so a wallet verifier can plug in as one more provider: it passes the provider contract tests (tests/unit/Service/SignerAuth/SignerAuthProviderContractTestCase.php) and registers, with no other change.

Next: connect your identity broker under Settings > Signer identity.


Request State Machine​

pending → in-progress → completed
↓
declined
↓
cancelled

isValidTransition() enforces allowed state transitions.


Audit Trail​

SigningAuditService records every lifecycle event (created, signed, declined, cancelled, verified) as an immutable entry in OpenRegister's native audit trail (openregister_audit_trails). Events are stored with action types of the form filinq.signing.{ACTION} (e.g. filinq.signing.SIGNED). The audit trail is hash-chained and natively immutable — update and delete operations are rejected by OR with HTTP 405.

Signing audit events are discoverable via:

GET /api/audit-trails?objectUuid={signRequestId}

Supported action types​

Action typeTriggered when
filinq.signing.CREATEDSigning request is created
filinq.signing.STARTSigner starts a signing session
filinq.signing.SIGNEDSigner signs the document
filinq.signing.DECLINEDSigner declines to sign
filinq.signing.CANCELLEDInitiator cancels the request
filinq.signing.EXPIREDRequest expires before completion
filinq.signing.COMPLETEDAll signers have signed
filinq.signing.VIEWEDSigner views the document

Each entry's changed JSON field carries: signRequestId, actorUserId, actorDisplayName, ipAddress, signatureLevel, provider.

Archiefwet 1995 retention configuration (required)​

Administrators MUST configure OR's retention for the signing register to ≥ 3650 days (10 years) to comply with Archiefwet 1995.

This is a deploy-time configuration in OpenRegister — not enforced by Filinq application code. Configure via:

  • OR Admin UI: Navigate to Settings → OpenRegister → Registers → signing register → set retention period to 3650 days or higher.
  • occ command (if available in your OR version):
    php occ openregister:register:set-retention <registerId> --days 3650

The legal basis is Archiefwet 1995, which requires a minimum 10-year retention period for signing audit records relating to official documents. Failure to configure this setting may result in audit entries being purged before the mandatory retention period expires.

Legacy signingAuditEntry records​

Prior to the migrate-signing-audit-to-or-audit release, signing events were stored in the signingAuditEntry OR schema. These records remain readable (read-only) for one major release. No new events are written to signingAuditEntry; all new events go to OR's native audit trail.


Services​

SigningService​

Orchestrates the signing request lifecycle.

MethodDescription
createRequest(data)Validate and create a new signing request
getRequest(id)Retrieve a signing request by ID
listRequests()List requests visible to the current user
sign(id, signerId)Record a signature action
decline(id, signerId, reason)Decline a signing request
cancelRequest(id)Cancel an open request
bulkSign(requestIds)Sign multiple requests at once
isValidTransition()Check whether a status transition is allowed

SigningVerificationService​

MethodDescription
verifyDocument(fileId, userId)Verify signature integrity for a file

SigningAuditService​

MethodDescription
logEvent()Record a signing lifecycle event
getAuditTrail(id)Retrieve all events for a signing request

SigningProviderFactory​

Resolves the configured signing provider instance.

SigningProviderInterface​

MethodDescription
getIdentifier()Provider identifier string
initiateSigning()Start a signing session with the provider
checkStatus(externalId)Poll external provider for signing status
downloadSignedDocument()Retrieve the signed document binary
cancelSigning(externalId)Cancel an external signing session
supportsLevel(level)Check if provider supports a signature level

Dependencies​

DependencyPurpose
OpenRegisterStorage for signing requests and audit entries
INotificationManagerNotify signers of pending requests
IUserSessionIdentify the current signing user
IAppConfigRead provider configuration