VerifyKitv0.13.3

Troubleshooting

Common issues and solutions when working with the VerifyKit SDK.


WASM Loading Failures

Symptom: CompileError: WebAssembly.instantiate() or error during WASM initialization.

Possible causes and solutions:

  1. Content Security Policy (CSP) blocks WASM compilation. WebAssembly compilation requires permission in script-src. Add wasm-unsafe-eval to your CSP header:

    Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval' blob:;
    
  2. Using a version older than 0.3.1. Those versions needed vite-plugin-wasm or webpack's asyncWebAssembly experiment. Update. No bundler plugin has been required since v0.3.1, on either side of the v0.12.0 change in how the binary is delivered.

  3. A deploy step that dropped the .wasm. From v0.12.0 the engine is a real file again. If your pipeline copies build output by extension, or uploads only *.js and *.css, the engine never reaches the server and the fetch 404s. Check that .wasm files in your build output are published.

  4. The server is not sending application/wasm. Verification still works, but the browser cannot stream-compile and logs a warning naming the type it got. Fix the MIME type on your static host.

  5. Custom setWasmUrl() pointing to a missing file. If you are using setWasmUrl() or wasmUrl config to load WASM from a custom location, ensure the file is accessible and served with application/wasm MIME type.

Note: no bundler configuration is required. Vite, webpack, Next.js, Rollup and Parcel all resolve the .wasm on their own. If loading fails on a current version, the likely causes are CSP, a deploy step that dropped the file, or a wrong MIME type — in that order.


Registry Not Configured

Symptom: npm ERR! 404 Not Found or npm ERR! code E404 when installing @trexolab packages.

Cause: npm does not know where to find the @trexolab scoped packages.

Solution: Configure .npmrc with the VerifyKit registry. Create a .npmrc file in your project root:

@trexolab:registry=https://verifykit.trexolab.com/api/registry

Or set it globally:

bash
npm config set @trexolab:registry https://verifykit.trexolab.com/api/registry

PowerShell users: Do not use > to create the .npmrc file -- it writes UTF-16 with a BOM, which npm cannot parse. Use [System.IO.File]::WriteAllText() instead:

powershell
[System.IO.File]::WriteAllText("$PWD\.npmrc", "@trexolab:registry=https://verifykit.trexolab.com/api/registry`n")

pdf.js Worker Version Mismatch

Symptom: The API version "X" does not match the Worker version "Y", worker errors, or blank pages where the PDF should render.

Cause: Since v0.6.0, pdf.js (5.5.207) is bundled inside @trexolab/verifykit-react — you do not install it, and npm ls pdfjs-dist will not show it unless something else in your project depends on it. But the worker is still loaded from the URL you pass as workerUrl, and pdf.js requires the worker to match the bundled library exactly.

So the mismatch is always between your workerUrl and the pinned version, not between two installed packages.

Solution: point workerUrl at 5.5.207:

tsx
<VerifyKitProvider
  config={{ workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs' }}
>

Or self-host it and keep the copy in step with the SDK — re-copy it whenever you upgrade VerifyKit:

bash
# copy the matching worker into your public/ directory
npm pack pdfjs-dist@5.5.207 --pack-destination /tmp \
  && tar -xzOf /tmp/pdfjs-dist-5.5.207.tgz package/legacy/build/pdf.worker.min.mjs \
     > public/pdf.worker.min.mjs

Before v0.6.0 pdfjs-dist was a runtime dependency you installed yourself and the advice was to pin it with npm install pdfjs-dist@5.5.207. That no longer applies — installing it separately has no effect on which pdf.js the viewer uses.


"workerUrl is required" Error

Symptom: Error message stating that workerUrl is required when initializing VerifyKitProvider or calling VerifyKit.create().

Cause: The workerUrl option is mandatory and was not provided in the configuration.

Solution: Pass the workerUrl option with the URL to the PDF.js worker script.

React:

tsx
<VerifyKitProvider config={{
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  theme: { mode: 'system' },
}}>

Vanilla JS:

ts
const viewer = VerifyKit.create(el, {
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
})

Worker Initialization Issues

Symptom: Blank viewer, console errors about worker initialization, or pdf.worker.min.mjs returning 404.

Possible causes and solutions:

  1. workerUrl is missing or incorrect. Ensure you are passing the workerUrl option with a valid URL to the PDF.js worker script. The recommended CDN URL is:

    https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs
    
  2. Self-hosted worker file is missing. If using a local path, ensure the file has been copied to your public directory:

    bash
    npm pack pdfjs-dist@5.5.207 --pack-destination /tmp \

&& tar -xzOf /tmp/pdfjs-dist-5.5.207.tgz package/legacy/build/pdf.worker.min.mjs \

public/pdf.worker.min.mjs


Then pass the local path:

```tsx
<VerifyKitProvider config={{ workerUrl: '/pdf.worker.min.mjs' }}>
  1. CSP blocks the worker URL. If the CDN is blocked by your Content Security Policy, either self-host the worker or add the CDN origin to your CSP:

    Content-Security-Policy: script-src 'self' https://unpkg.com; worker-src 'self' https://unpkg.com;
    

Tailwind CSS Conflicts

Symptom: Viewer UI elements look broken -- buttons have no background, borders are missing, images are stretched, SVG icons display as block elements.

Cause: Tailwind's preflight (CSS reset) overrides default browser styles for button, img, svg, and border properties. Since the VerifyKit viewer relies on default browser styles for many elements, the reset breaks the UI.

Solution: Add CSS overrides scoped to .verifykit-root to restore the expected styles:

css
.verifykit-root img {
  display: inline;
  max-width: none;
}
 
.verifykit-root button {
  background: revert;
  border: revert;
  padding: revert;
  font: revert;
  color: revert;
  cursor: pointer;
}
 
.verifykit-root svg {
  display: inline;
}
 
.verifykit-root *,
.verifykit-root *::before,
.verifykit-root *::after {
  border-style: revert;
  border-width: revert;
}

Place these rules after Tailwind's @tailwind base import so they take precedence.


Adobe Reader Messages and What They Map To

Symptom: A user reports an error from Adobe Acrobat Reader, quoting its exact wording, and you need to know which VerifyKit check corresponds to it.

Adobe collapses eight independent checks into a handful of sentences, so its messages map onto VerifyKit's result fields many-to-one. This table gives the direction to look:

Adobe Reader messageCorresponding fieldUsual cause
"At least one signature has problems"sig.overallStatus on any signature is not validA document-level banner reporting the worst signature. Not a diagnosis — inspect each signature separately.
"Signature validity is UNKNOWN"certificateChainCheck.statusThe signer's certificate does not chain to a trusted root. See Signatures Showing as "unknown" below.
"The signer's identity is unknown because it has not been included in your list of trusted certificates"certificateChainCheck.statusThe same trust failure, worded from the trust-store side. Add the root CA via trustStore.
"The document has been altered or corrupted since it was signed"integrityCheck.status, byteRangeCoversWholeFileEither a genuine hash mismatch (invalid) or content appended after the signed byte range (valid/warning). These are different situations — see below.
"There have been changes made to this document that invalidate the certification signature"integrityCheck.status after DocMDP rules, mdpPermissionA certification signature with mdpPermission === 1 (no changes allowed) followed by an incremental update.
"At least one signature requires validation"revocationCheck.attempted === falseValidation has not been run, or revocation was never checked. See Revocation Check Shows "Not checked (offline)".
"Signature is INVALID"signatureCheck.statusThe CMS/PKCS#7 signature does not verify against the signer's public key.
Timestamp reported as unverified in Signature PropertiestimestampCheck.status, sig.timestamp.verifiedThe RFC 3161 timestamp is present but its TSA certificate does not validate.

On "altered or corrupted" specifically: byteRangeCoversWholeFile === false is not by itself an error. A PDF is designed to be appended to, so every signature except the last one in a multi-signature document legitimately covers only part of the file, and long-term validation (DSS) data appended after signing is a permitted update. Read integrityCheck.status together with the coverage flag:

integrityCheck.statusbyteRangeCoversWholeFileMeaning
invalideitherHash mismatch. The signed bytes really did change.
validtrueWhole file covered and unmodified.
validfalseSigned content intact; later signatures or DSS/LTV data followed. Normal.
warningfalseSigned content intact, but unsigned non-DSS content follows the last signature.

The detail string on each SignatureCheckResult states which of these applies in words, so it can be surfaced directly in a UI rather than re-derived.


Signatures Showing as "unknown"

Symptom: Signature overallStatus is "unknown" and the certificate chain check fails, even though the PDF is validly signed.

Cause: The signing certificate does not chain to any of the 134 embedded root CA certificates (118 AATL, 16 Indian CCA). This is common with enterprise or self-signed certificates.

Solution: Add your organization's root CA certificate via the trust store configuration:

typescript
const verifier = await createVerifier({
  trustStore: {
    certificates: [pemString],
    mode: 'merge', // adds your cert alongside the 134 built-in roots
  },
})

In React:

tsx
<VerifyKitProvider config={{
  workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
  trustStore: {
    certificates: [myCompanyRootCaPem],
    mode: 'merge',
  },
}}>

Use mode: 'replace' if you want only your certificates to be trusted and want to ignore the built-in AATL store entirely.


Signature Shows "valid" in Engine But "unknown" in UI

Symptom: sig.overallStatus is "valid" but the UI displays "Signature validity is unknown."

Cause: The UI layer uses getDisplayStatus() for Adobe Reader parity. When the chain is trusted but revocation can't be checked (common in browsers due to CORS blocking OCSP/CRL endpoints), the display status is downgraded to "unknown".

Solution: Install the revocation plugin to provide online OCSP/CRL checking via a server-side proxy:

bash
npm install @trexolab/verifykit-plugin-revocation

Or, in the React UI, use sig.overallStatus instead of getDisplayStatus(sig) if you want the raw engine result without Adobe parity adjustments.

For Electron/Tauri/browser-extension hosts, bridge the revocation fetch through the native layer instead of a proxy — see Desktop & Extension Viewers.


Signature Shows as "Certified" When It Shouldn't

Symptom: A non-certified PDF shows "Certified by..." in the signature panel or document message bar.

Cause: The WASM engine serializes Option::None as undefined (not null). If your code checks sig.mdpPermission !== null, it misses undefined and treats the signature as certified.

Solution: Always use loose equality when checking mdpPermission:

ts
// Correct
if (sig.mdpPermission != null) { /* certified */ }
 
// Wrong — misses undefined
if (sig.mdpPermission !== null) { /* bug: treats undefined as certified */ }

Appearance Icon Not Showing in PDF

Symptom: The signature appearance in the PDF canvas doesn't show the verification status icon (green tick, red cross, yellow question mark).

Possible causes:

  1. The field's appearance cannot carry a status icon. VerifyKit works with the signature appearance styles produced by Acrobat and the common signing tools. A field authored with a plain, single-layer appearance has nowhere to put the icon, and is left as the signer authored it rather than drawn over. Call hasAcro6Appearances() to check a document up front.

  2. The signing tool deliberately left no icon slot. Some tools author a signature appearance with the status area intentionally blank. VerifyKit respects that and does not inject an icon.

  3. swapSignatureAppearances() not called. The icon swap is a separate step after verification. Ensure you call it and use the modified PDF bytes for display.


SSR / Server-Side Rendering Issues

Symptom: ReferenceError: DOMMatrix is not defined, ReferenceError: document is not defined, or TypeError: Object.defineProperty called on non-object when a framework renders VerifyKit on the server.

Cause: On v0.5.14 and earlier, @trexolab/verifykit-react imported pdf.js at module scope. That import was evaluated during a server render and threw before any component rendered — which is why no typeof window guard helped.

Fixed in v0.6.0. pdf.js is now loaded lazily, inside the effects that use it, and bundled into its own chunk. The package also ships a 'use client' directive, so it is its own client boundary. Server rendering works with no configuration:

tsx
// app/page.tsx — a Server Component. No 'use client', no next/dynamic.
import { VerifyKitProvider, Viewer } from '@trexolab/verifykit-react'
import '@trexolab/verifykit-react/styles.css'
 
export default function Page() {
  return (
    <VerifyKitProvider config={{ workerUrl: '/pdf.worker.min.mjs' }}>
      <Viewer />
    </VerifyKitProvider>
  )
}

Verified server-rendering and hydrating with zero hydration warnings under Turbopack and webpack, in dev and production builds.

If you are still seeing SSR errors:

  1. Upgrade to v0.6.0 or later. On older versions the only workaround is dynamic(() => import('./MyViewer'), { ssr: false }), which still works on 0.6.0 if you already have it.
  2. Add 'use client' to your own file if that file uses hooks itself — calling useVerification() makes it a client component regardless of what the SDK does.
  3. Do not add a canvas alias. VerifyKit renders through PDF.js's browser canvas and never imports the Node canvas package; the config.resolve.alias.canvas = false workaround is unnecessary.

The headless @trexolab/verifykit-core package works in Node.js without restrictions -- only the viewer UI components require a browser environment.


Dark Mode Not Working

Symptom: Theme does not switch, or the viewer always renders in light mode.

Possible causes and solutions:

  1. <VerifyKitProvider> is not wrapping your viewer components. The provider sets the data-theme attribute on <html>. Without it, theme switching has no effect.

  2. Theme mode is not configured. Set the theme mode explicitly:

    tsx
    <VerifyKitProvider config={{
      workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
      theme: { mode: 'system' },
    }}>

    Valid values: 'light', 'dark', 'system' (follows OS preference).

  3. Custom CSS does not target the correct attribute. For CSS variable theming, target the data-theme attribute on .verifykit-root:

    css
    .verifykit-root[data-theme="dark"] {
      --bg: #1a1a1a;
      --fg: #e0e0e0;
    }
  4. Multiple viewers conflict. The theme system sets data-theme on <html>, which is global. Multiple VerifyKitProvider instances with different theme modes will conflict. Use a single provider or ensure all viewers use the same theme mode.


Performance Issues

Symptom: Slow initial load, laggy scrolling, or high memory usage.

Possible causes and solutions:

  1. First WASM load takes ~100-200ms. This is expected. The WASM module is cached in memory after the first load, so subsequent calls to createVerifier() are instant. For the best user experience, initialize the verifier early (e.g., on app startup).

  2. Large PDFs (100+ pages) may take longer to render. Use defaultLayoutPlugin() which includes lazy rendering by default -- only visible pages are rendered.

  3. Memory grows in single-page applications. Always call destroy() when removing a vanilla viewer instance. For React, ensure the viewer unmounts properly during navigation. If using the vanilla API inside a React useEffect, return a cleanup function:

    typescript
    useEffect(() => {
      const v = VerifyKit.create(containerRef.current!, {
        workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
      })
      v.load(buffer, 'doc.pdf')
      return () => v.destroy()
    }, [])

TypeScript "Cannot find module" Errors

Symptom: TypeScript reports Cannot find module '@trexolab/verifykit-react' or Cannot find module '@trexolab/verifykit-react/styles.css'.

Possible causes and solutions:

  1. TypeScript version or module resolution is outdated. Ensure you are using TypeScript 5.0+ with moduleResolution: "bundler" in your tsconfig.json:

    json
    {
      "compilerOptions": {
        "moduleResolution": "bundler"
      }
    }

    The packages export proper type definitions via the exports field in package.json, which requires "bundler" or "node16" module resolution.

  2. CSS import not recognized. TypeScript does not resolve .css imports by default. Add a type declaration:

    typescript
    // src/global.d.ts
    declare module '*.css' {}

ERR_MODULE_NOT_FOUND for pkg/verifykit_core_wasm in Node.js

Symptom: in Node.js, Deno or Bun — but never in the browser — await initWasm(), createVerifier() or verifyPdf() throws:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module
  '…/node_modules/@trexolab/verifykit-core/pkg/verifykit_core_wasm'

Cause: on 0.6.4 and earlier, the built bundle imported its wasm-bindgen glue without a file extension. Bundlers resolve an extensionless specifier, so every browser integration worked; Node's ESM resolver requires the extension and fails.

Solution: upgrade to 0.6.5 or later:

bash
npm install @trexolab/verifykit-core@latest

There is no workaround on older versions short of bundling the package yourself — the specifier is baked into the published dist.


"Cannot use import statement outside a module" in Node.js

Symptom: SyntaxError: Cannot use import statement outside a module when running @trexolab/verifykit-core in Node.js.

Cause: Your Node.js project is configured for CommonJS, but the package uses ES module syntax.

Solution: Either:

  • Add "type": "module" to your package.json, or
  • Ensure your bundler resolves require('@trexolab/verifykit-core') to the CJS entry point, or
  • Rename your file to .mjs

Revocation Check Shows "Not checked (offline)"

Symptom: revocationCheck.status is "unknown" and revocationCheck.detail says "Not checked (offline)."

Cause: The base @trexolab/verifykit-core does not perform online revocation checking. It only reads embedded CRL/OCSP data from the PDF.

Solution: Install and configure the revocation plugin:

bash
npm install @trexolab/verifykit-plugin-revocation
typescript
import { createVerifier } from '@trexolab/verifykit-core'
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
 
const verifier = await createVerifier({
  plugins: [revocationPlugin()],
})

"VerifyKit viewer failed to initialize within 10 seconds"

Symptom: Timeout error when calling viewer.load() on a vanilla instance.

Cause: The internal React root did not mount in time. This can happen if:

  • The container element is not visible or is detached from the DOM
  • The WASM module failed to load silently
  • JavaScript execution is blocked

Solution:

  • Ensure the container element is attached to the document and visible before calling VerifyKit.create()
  • Check the browser console for WASM loading errors
  • Verify that .wasm files are accessible from your hosting environment

CJK Text Not Rendering

Symptom: CJK (Chinese, Japanese, Korean) characters display as squares or boxes.

Cause: PDF.js requires CMap files for CJK text rendering. These files are not bundled by VerifyKit.

Solution: Copy CMap files from pdfjs-dist to your public directory:

bash
cp -r node_modules/@trexolab/verifykit-react/cmaps public/cmaps
cp -r node_modules/@trexolab/verifykit-react/standard_fonts public/standard_fonts

AIA Certificate Fetching Failures

Symptom: Certificate chain check fails with unknown status, even though the signer's root CA is in the trust store. The PDF does not embed intermediate certificates.

Cause: AIA (Authority Information Access) resolution is enabled by default, but the AIA URL may be unreachable due to network restrictions, firewalls, or DNS issues.

Possible solutions:

  1. Check network access. Ensure outbound HTTP is allowed to CA infrastructure URLs. Test with curl <aia-url>.

  2. Inspect the certificate's AIA URLs. Check cert.caIssuersUrls in the verification result:

    ts
    const result = await verifier.verify(buffer)
    for (const sig of result.signatures) {
      console.log('CA Issuers:', sig.signerCertificate?.caIssuersUrls)
    }
  3. Disable AIA for air-gapped environments. If no outbound network access is available, disable AIA and ensure PDFs embed the full chain:

    ts
    const verifier = await createVerifier({ enableAIA: false })
  4. Timeout issues. AIA fetches use a default timeout. If endpoints are slow, the fetch may time out silently. Check browser or Node.js console for network errors.


Encrypted or Password-Protected PDFs

Symptom: The viewer prompts for a password, or verification fails with an error about encrypted content.

Cause: The PDF is encrypted with a user or owner password. VerifyKit can verify signatures in encrypted PDFs, but requires the correct password to render the document.

Possible solutions:

  1. Enter the password when prompted. The React viewer shows a <PasswordDialog> automatically when an encrypted PDF is loaded. Enter the user password to unlock the document.

  2. Programmatic password supply. When using the headless core API, pass the password as part of the input:

    ts
    // Encrypted PDFs are supported for signature verification
    // The WASM engine can extract and verify signatures from
    // encrypted PDFs without needing the password.
    const result = await verifier.verify(buffer, 'encrypted.pdf')
  3. Owner password only. If the PDF has an owner password (for permission restrictions) but no user password, it can be opened without a password. The permissions object in the verification result will reflect the restrictions.

  4. Check permissions. Use extractPdfMetadata() to inspect the encryption status:

    ts
    const { permissions } = await extractPdfMetadata(buffer)
    console.log('Encrypted:', permissions.encrypted)
    console.log('Method:', permissions.encryptionMethod)

Encrypted PDF still shows the yellow "?" on the page

Symptom: The signature panel says "Signature is valid" and the signature properties dialog is correct, but the signature drawn on the page still shows Adobe's yellow question mark instead of the green validated check.

Cause: That question mark is part of the document — the placeholder the signing tool authored into the signature field. VerifyKit replaces it with the real status icon after verification. On an encrypted PDF that replacement needs the password the viewer accepted; verification itself never needs it, which is why every textual result is already correct while only the on-page icon lags.

Fixed in 0.5.14. The viewer now hands the accepted password to useVerification automatically through VerifyKitProvider, so this works in every integration — React (Viewer, CoreViewer), vanilla, and Vue — with no wiring in your app. Before 0.5.14 it only worked if the host happened to pass onPasswordAccepted={verification.applyPassword} by hand.

If you are still seeing it:

  1. Upgrade to 0.5.14 or later, and make sure <Viewer> is rendered inside the same <VerifyKitProvider> as the useVerification() call — that provider is what carries the password between them.
  2. Check the cipher. Documents using the AES-128 Standard security handler are supported. Other ciphers are left untouched by design, so the "?" stays rather than risking a corrupted document.
  3. Owner vs user password. A document unlocked with the owner password rather than the user password cannot be rewritten safely. VerifyKit detects this and leaves the PDF untouched instead of producing a file nothing can open.

In every fallback case the original bytes are returned unchanged — the on-page icon is display polish, and it is never allowed to alter a signed document.


"structuredClone is not defined"

Symptom: ReferenceError: structuredClone is not defined at runtime.

Cause: Your browser or Node.js version is too old. structuredClone requires Node.js 17+ or a modern browser from 2022+.

Solution: Upgrade to Node.js 20+ or a supported browser (Chrome 109+, Firefox 115+, Safari 16.4+, Edge 109+). There is no polyfill path for structuredClone in VerifyKit.