Document Timestamp

This walkthrough takes an existing PDF through preparation, a timestamp request, and validation. The API receives a digest and returns a timestamp response. The downloadable script handles PDF preparation and token embedding.

A document timestamp provides evidence that the covered document state existed at the TSA's asserted time. It does not identify a human signer.

To timestamp a signature while creating it, see Signature Timestamp.

1. Set up access and tools

Obtain your client ID and allowlist your server's public outbound IP in eID Easy adminopen in new window: My Webpages → EDIT → Allowed IP addresses. See the original timestamping guide for the access instructions and Postman example.

You need Python 3.10+, OpenSSL 3+ available as openssl, curl 7.76+, and an unencrypted PDF with at least one page.

Download timestamping-examples.zip and extract it. It contains timestamp_pdf.py and a sample original.pdf; you can also use your own PDF. In the extracted directory, install the PDF library:

python3 -m venv .venv
. .venv/bin/activate
python -m pip install 'pyhanko==0.35.1'

2. Prepare the PDF

python timestamp_pdf.py prepare original.pdf work

Use a new directory for work. This creates:

  • work/prepared.pdf: the PDF with an invisible document timestamp field and reserved token space.
  • work/prepared.json: its SHA-256 digest, byte ranges and reservation metadata.

Keep these files unchanged until completion. The digest covers the prepared PDF's /ByteRange, excluding the reserved /Contents value. Hashing the original PDF or the entire prepared PDF produces the wrong digest.

The script uses an incremental update to retain existing PDF bytes. If the PDF already has signatures, also validate those signatures and their modification permissions.

3. Generate and send the request

python timestamp_pdf.py request work

This creates work/request.tsq: a binary RFC 3161open in new window request using SHA-256, requesting the TSA certificate, and omitting the optional nonce. To inspect it:

openssl ts -query -in work/request.tsq -text

Send it using your configured client ID:

export CLIENT_ID='YOUR_CLIENT_ID'
curl --silent --show-error --fail-with-body \
  "https://id.eideasy.com/api/timestamp/${CLIENT_ID}" \
  --header 'Content-Type: application/octet-stream' \
  --data-binary @work/request.tsq \
  --output work/response.tsr

The body must be the binary request file. Stop if curl fails. The saved response may contain a JSON error instead of a timestamp; check its message and Case ID before continuing.

4. Validate the response and embed the token

python timestamp_pdf.py complete work

The script includes the public SK TSA root and intermediate from SK ID Solutionsopen in new window, so no separate certificate files are needed. It checks the TSA status, digest, signature, certificate chain and token capacity, then creates:

  • work/timestamp-token.der: the extracted CMS timestamp token.
  • work/timestamped.pdf: the completed PDF.

The full HTTP response is a TimeStampResp wrapper. Only its CMS token belongs in the PDF's /Contents; the script inserts it without changing the covered bytes. Do not embed the full response or Base64 text, or rewrite the PDF after preparation.

5. Validate the completed PDF

python timestamp_pdf.py validate work/timestamped.pdf

For a successful result, expect intact=True, valid=True, trusted=True, and coverage=SignatureCoverageLevel.ENTIRE_FILE. Validation checks the newest document timestamp using the bundled TSA chain and available trust store, and may fetch revocation information.

Open work/timestamped.pdf in a PDF viewer and inspect its Signatures panel. A digest mismatch means the covered bytes differ. If the TSA is unknown, configure trust in your validator; the script also accepts optional --ca-file and --chain-file arguments. A document timestamp alone does not establish long-term validation or qualified status.

Troubleshooting

ProblemWhat to check
Request deniedClient ID and public outbound IP allowlist.
HTTP 500Error message and Case ID in work/response.tsr; stop before completion.
Digest mismatchUse the matching prepared PDF, metadata, request and response; do not change covered bytes.
Token too largePrepare again in a new directory with --token-capacity 32768, then request a fresh timestamp.
Unknown TSA or missing revocation dataTrust roots, intermediate certificates and OCSP/CRL access.
grantedWithModsInspect the response and policy before using complete --accept-mods.

Optional: use a prepared PDF or an existing digest

To skip preparation, download prepared.pdf and its matching prepared.json into work, then continue from step 3. The expected digest and byte ranges are in the JSON file.

If another component already supplies the prepared PDF's Base64 SHA-256 digest:

python timestamp_pdf.py request work --digest-base64 'YOUR_BASE64_SHA256_DIGEST'

SHA-256 is 32 raw bytes or 44 characters in padded Base64. Decode the digest text rather than hashing it again. The digest must belong to the exact prepared PDF you will complete.

Last Updated: