Documenso

CSC (AES / QES)

Configure Cloud Signature Consortium signing for Advanced and Qualified Electronic Signatures via a third-party Trust Service Provider.

The csc signing transport routes signatures through a third-party Trust Service Provider (TSP) using the Cloud Signature Consortium API v1.0.4.0. Each recipient authenticates directly with the TSP (Strong Customer Authentication) and the TSP returns a per-recipient signature bound to the document hash. Documenso assembles the resulting PAdES signature inside the PDF.

This transport enables Advanced Electronic Signatures (AES) and Qualified Electronic Signatures (QES) under eIDAS. See Signature Levels for the legal framework.

CSC mode is instance-wide: one CSC provider per Documenso install. All envelopes created while the instance runs in csc mode use AES or QES. Switching NEXT_PRIVATE_SIGNING_TRANSPORT is a one-way operational migration — see Switching Transports.

CSC mode requires an active Enterprise Edition license. The instance refuses to start in csc mode without it.

Prerequisites

A TSP account

Establish a relationship with a CSC-compatible Trust Service Provider. The TSP issues qualified or advanced certificates to your signers, holds the private keys in its HSM, and exposes a CSC v1.0.4.0-compliant API.

OAuth client credentials

Register Documenso as an OAuth client with the TSP. You will receive a client ID and client secret, and must supply Documenso's callback URL when registering:

${NEXT_PUBLIC_WEBAPP_URL}/api/csc/oauth/callback

The callback URL is fixed — Documenso derives it from NEXT_PUBLIC_WEBAPP_URL and the route mount path. There is no env var to override it; ensuring the registered URL matches your instance's webapp URL exactly is the operator's responsibility.

Enterprise Edition license

CSC mode is gated by the instanceCscSigning license flag. Without a valid Enterprise license, the transport refuses to start (CSC_UNLICENSED).

CSC produces multiple DocumentData rows per envelope item (one per recipient signature, plus the materialised and source rows). Database-backed storage base64-inflates each row by ~33% and is impractical at meaningful PDF sizes. Configure S3 storage before enabling CSC.

Environment Variables

VariableDescriptionDefault
NEXT_PRIVATE_SIGNING_TRANSPORTSet to csc
NEXT_PRIVATE_SIGNING_CSC_PROVIDER_BASE_URLBase URL of the CSC provider's API
NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_IDOAuth client ID registered with the CSC provider
NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_SECRETOAuth client secret registered with the CSC provider
NEXT_PRIVATE_SIGNING_CSC_SIGNATURE_LEVELDefault legal tier for new envelopes when the caller does not specify one. AES or QES. Explicit requests always pass through.AES
NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITYRequired. Comma-separated RFC 3161 TSA URLs. Always used for B-LTA archival timestamps at seal time, and also serves as the B-T sign-time fallback when the TSP does not expose signatures/timestamp. The instance refuses to start in CSC mode without it. See Timestamp Authority Resolution.

NEXT_PUBLIC_SIGNING_TRANSPORT_IS_CSC is set automatically from NEXT_PRIVATE_SIGNING_TRANSPORT at server startup. Do not set it manually — see Environment Variables.

Configuration Example

NEXT_PRIVATE_SIGNING_TRANSPORT=csc
NEXT_PRIVATE_SIGNING_CSC_PROVIDER_BASE_URL=https://api.example-tsp.com/csc/v1
NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_ID=documenso-prod
NEXT_PRIVATE_SIGNING_CSC_OAUTH_CLIENT_SECRET=...
NEXT_PRIVATE_SIGNING_CSC_SIGNATURE_LEVEL=QES
NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY=http://timestamp.example.com

Register ${NEXT_PUBLIC_WEBAPP_URL}/api/csc/oauth/callback (e.g. https://sign.example.com/api/csc/oauth/callback) as the OAuth callback URL with the TSP.

Default Signature Level

NEXT_PRIVATE_SIGNING_CSC_SIGNATURE_LEVEL selects the legal tier applied to envelopes that do not specify one explicitly. It is a default, not a capability gate: callers may still create AES or QES envelopes explicitly regardless of this setting.

Configured valueCaller passes nothingCaller passes AESCaller passes QES
AES (default)Envelope is AESEnvelope is AESEnvelope is QES
QESEnvelope is QESEnvelope is AESEnvelope is QES

Any value other than AES or QES causes the instance to refuse to start. This prevents silent qualified-to-advanced downgrades from a typo.

Timestamp Authority Resolution

AES/QES envelopes use TSA-attested timestamps in two distinct phases. Resolution differs per phase.

Sign time — PAdES B-T per recipient

Each recipient's CMS embeds a signature timestamp (CMS unsigned attribute) so proven time is bound to the recipient's signature itself. Resolution order:

  1. If the TSP advertises signatures/timestamp in its info response (CSC §11.10), the TSP endpoint is used. The call is authorised with this recipient's service-scope bearer token — the same one authorising the signatures/signHash call alongside it.
  2. Otherwise, the first URL from NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY is used (RFC 3161 over HTTP).

Selection is made at boot from the discovered transport, not at runtime; there is no try-then-fall-through. If the chosen source fails, the recipient's sign attempt fails.

Seal time — PAdES B-LTA archival

The seal-document job emits a single archival /DocTimeStamp over the fully-signed envelope (plus DSS for the existing signatures and the timestamp's own chain). This phase is env-only: the first URL from NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY is always used.

The archival anchor is the operator's long-term trust anchor and SHOULD point at a dedicated qualified archival TSA (e.g. DigiCert) independent of the per-recipient TSP. We deliberately do not fall back to the TSP at seal time: archive longevity should not be coupled to a TSP that may rotate or revoke, and the seal-document job has no recipient context to carry a service-scope bearer.

Boot-time guard

The instance refuses to start in CSC mode unless NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY is set (CSC_PROVIDER_NO_TSA at transport construction). The env var is required unconditionally — even when the TSP advertises its own signatures/timestamp, seal-time B-LTA archival uses the env TSA. Catching this at boot prevents the failure mode where an envelope signs successfully at B-T and then hangs in WAITING_FOR_SIGNATURE_COMPLETION when the seal job throws.

Switching Transports

NEXT_PRIVATE_SIGNING_TRANSPORT is a one-way operational migration. Existing envelopes route per the signatureLevel column they were created with — the runtime branching looks at the envelope, not the env var. After a switch:

  • Envelopes already at SES continue to use the new transport for sealing, but the new transport's signer must produce SES-compatible signatures (only local and gcloud-hsm qualify).
  • Envelopes already at AES / QES will fail at sign or seal time if the new transport is not csc.

Plan migrations during a quiet window with no in-flight envelopes.

Behavioural Notes

CSC mode changes a number of envelope-authoring behaviours that operators should communicate to users.

Mutation lock at distribution

For AES/QES envelopes, all authoring routes refuse mutations once the envelope leaves DRAFT. This locks the PDF before any recipient begins Strong Customer Authentication, closing the PDF-swap window that would otherwise allow an owner to replace the PDF between view and sign and break the legal "what you see is what you sign" guarantee.

In practice: edit envelope, recipients, fields, and items freely while DRAFT; once sent, no changes are accepted (including from the API).

Sequential signing only

Parallel signing produces conflicting incremental updates over the same base PDF, breaking the per-recipient /ByteRange invariant. The signing order is forced to SEQUENTIAL on AES/QES envelopes — at the schema layer, at send time, and in the UI (the parallel-signing toggle is hidden).

Assistant role and Dictate Next Signer disabled

Both features modify the recipient set after the envelope is sent, which is incompatible with the AES/QES mutation lock. They are hidden in the UI and rejected at the server schema layer.

Sidecar PDFs at download

The signed PDF must remain byte-identical to what each recipient's TSP signature authorised — Documenso cannot decorate it after signing. Audit logs and the Certificate of Completion are generated on demand and delivered as separate PDFs:

  • GET /sign/{token}/download returns the signed PDF only (or a ZIP for multi-item envelopes).
  • GET /sign/{token}/download?version=bundle returns a ZIP containing the signed PDFs, audit log PDF, and Certificate of Completion.
  • The completion email attaches all three.

Recipient Flow

For context when supporting end users, here is what a recipient experiences on an AES/QES envelope:

  1. Opens the email link, lands on the signing page.
  2. Documenso redirects to the TSP for Strong Customer Authentication (first visit only; cached for the session lifetime).
  3. Fills fields as normal.
  4. Clicks Sign → redirected to the TSP for a second authentication round (issues a per-document Signature Activation Data token).
  5. Returns to Documenso; the signing call completes within ~15 seconds.
  6. Sees the standard completion screen.

If the TSP returns no eligible credentials for the recipient (e.g. they have not enrolled), they see a blocking page directing them to enrol with the TSP and retry.

Error Codes

CSC-specific error codes surfaced through the standard error channels:

CodeMeaningRecovery
CSC_UNLICENSEDLicense flag absent at transport-createOperator: enable Enterprise Edition, restart
CSC_PROVIDER_INFO_FAILEDinfo discovery failed at startupOperator: check TSP availability and NEXT_PRIVATE_SIGNING_CSC_PROVIDER_BASE_URL
CSC_PROVIDER_NO_TSANEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY is unsetOperator: configure NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY
CSC_CREDENTIAL_LIST_EMPTYTSP returned no credentials for the userRecipient: enrol with the TSP
CSC_CERT_INVALIDCertificate refused at credential validationRecipient: contact the TSP
CSC_ALGORITHM_REFUSEDSignature algorithm fails policyOperator/recipient: TSP does not meet policy (see below)
CSC_SAD_EXPIRED_PRE_SIGNSignature Activation Data expired before signingRecipient: retry from Sign
CSC_TSP_TIMEOUT15-second synchronous timeout reachedRecipient: retry (idempotent — the TSP enforces single-use SAD binding)
CSC_EMBED_FAILEDSign-time digest diverged from prep captureRecipient: retry from Sign
CSC_BASE_DOCUMENT_MUTATEDDocument data changed between prep and signOperator: investigate (structural guard violation)
CSC_INSTANCE_MODE_MISMATCHEnvelope created with wrong level for transportCaller: use a level matching the instance transport
CSC_REQUEST_FAILEDTSP HTTP transport failure — network error, non-2xx, or malformed responseOperator: check TSP availability; carries the TSP HTTP status and error in the message

Algorithm Policy

Documenso refuses TSP credentials that do not meet the following minimums, at the OAuth callback boundary and again at sign time:

ClassAllowedRefused
RSAkey.len >= 2048Missing key.len, key.len < 2048
ECDSAP-256, P-384, P-521Missing key.curve, P-192, P-224, other curves
HashSHA-256, SHA-384, SHA-512SHA-1, MD5
OtherDSA

This is the union of CSC v1.0.4.0 §11.5 requirements and current cryptographic guidance.

On this page