Skip to content

Integration Examples

Document Verification

Create a tamper-evident audit trail for document signatures using facts.

This example shows how to create a tamper-evident audit trail for documents using PacSpace's Facts API. Record document signatures, and later verify that a document hasn't been altered since it was signed.


Prerequisites

  • Node.js 18+
  • A PacSpace API key

Full Example

javascript
const crypto = require('crypto');

const API_BASE = 'https://balance-api.pacspace.io';
const API_KEY = process.env.PACSPACE_API_KEY; // pk_live_PUBLIC.SECRET

// Helper for API calls
async function pacspace(method, path, body = null) {
  const options = {
    method,
    headers: {
      'X-Api-Key': API_KEY,
      'Content-Type': 'application/json'
    }
  };
  if (body) options.body = JSON.stringify(body);

  const response = await fetch(`${API_BASE}${path}`, options);
  const data = await response.json();

  if (!response.ok) {
    throw new Error(`PacSpace error ${response.status}: ${data.message}`);
  }
  return data;
}

// --- Sign a Document ---

async function signDocument(document) {
  // Create a deterministic hash of the document content
  const documentHash = crypto
    .createHash('sha256')
    .update(typeof document.content === 'string'
      ? document.content
      : JSON.stringify(document.content))
    .digest('hex');

  // Build the signature record
  const signatureRecord = JSON.stringify({
    documentId: document.id,
    documentHash: documentHash,
    signedBy: document.signer,
    signedAt: new Date().toISOString(),
    title: document.title
  });

  // Record as a verified fact
  const result = await pacspace('POST', '/api/v1/facts', {
    content: signatureRecord,
    metadata: {
      type: 'document_signature',
      documentId: document.id,
      signer: document.signer,
      title: document.title
    }
  });

  console.log(`Document signed successfully`);
  console.log(`  Document:     ${document.title}`);
  console.log(`  Signer:       ${document.signer}`);
  console.log(`  Content Hash: ${result.data.contentHash}`);

  return {
    documentId: document.id,
    documentHash,
    contentHash: result.data.contentHash,
    signedAt: new Date().toISOString()
  };
}

// --- Verify a Document ---

async function verifyDocument(document, contentHash) {
  // Recompute the document hash
  const currentHash = crypto
    .createHash('sha256')
    .update(typeof document.content === 'string'
      ? document.content
      : JSON.stringify(document.content))
    .digest('hex');

  // Retrieve the original fact
  const fact = await pacspace('GET', `/api/v1/facts/${contentHash}`);
  const originalRecord = JSON.parse(fact.data.content);

  // Compare hashes
  const hashMatch = currentHash === originalRecord.documentHash;

  // Check verification status
  const verification = await pacspace('GET', `/api/v1/facts/${contentHash}/verify`);
  const isVerified = verification.data.verified;

  const result = {
    documentId: document.id,
    title: document.title,
    hashMatch,
    verified: isVerified,
    tampered: !hashMatch,
    originalHash: originalRecord.documentHash,
    currentHash,
    signedBy: originalRecord.signedBy,
    signedAt: originalRecord.signedAt,
    verifiedAt: verification.data.verifiedAt || null
  };

  if (result.tampered) {
    console.log(`\n  VERIFICATION FAILED — Document has been tampered with!`);
    console.log(`  Original hash: ${result.originalHash}`);
    console.log(`  Current hash:  ${result.currentHash}`);
  } else if (!result.verified) {
    console.log(`\n  Document integrity intact — verification pending`);
  } else {
    console.log(`\n  VERIFIED — Document is authentic and untampered`);
    console.log(`  Signed by:    ${result.signedBy}`);
    console.log(`  Signed at:    ${result.signedAt}`);
    console.log(`  Verified at:  ${result.verifiedAt}`);
  }

  return result;
}

// --- Multi-Party Signing ---

async function multiPartySign(document, signers) {
  const signatures = [];

  for (const signer of signers) {
    const docWithSigner = { ...document, signer };
    const sig = await signDocument(docWithSigner);
    signatures.push(sig);
  }

  // Record the complete signing event as a summary fact
  const summaryContent = JSON.stringify({
    documentId: document.id,
    title: document.title,
    signatures: signatures.map(s => ({
      signer: s.documentId,
      contentHash: s.contentHash,
      signedAt: s.signedAt
    })),
    allPartiesSigned: true,
    completedAt: new Date().toISOString()
  });

  const summary = await pacspace('POST', '/api/v1/facts', {
    content: summaryContent,
    metadata: {
      type: 'multi_party_signature',
      documentId: document.id,
      signerCount: signers.length
    }
  });

  console.log(`\nAll ${signers.length} parties signed. Summary hash: ${summary.data.contentHash}`);
  return { signatures, summaryHash: summary.data.contentHash };
}

// --- Run the full flow ---

async function main() {
  // Example document
  const document = {
    id: 'contract-2025-001',
    title: 'Service Agreement — Acme Corp',
    content: 'This agreement between Party A and Party B establishes...',
    signer: 'alice@acme.com'
  };

  // Step 1: Sign the document
  console.log('=== Signing Document ===\n');
  const signature = await signDocument(document);

  // Step 2: Verify the original document
  console.log('\n=== Verifying Original Document ===');
  await verifyDocument(document, signature.contentHash);

  // Step 3: Detect tampering
  console.log('\n=== Verifying Tampered Document ===');
  const tamperedDoc = {
    ...document,
    content: 'This agreement has been MODIFIED...'
  };
  await verifyDocument(tamperedDoc, signature.contentHash);

  // Step 4: Multi-party signing
  console.log('\n=== Multi-Party Signing ===\n');
  await multiPartySign(document, ['alice@acme.com', 'bob@partner.com', 'carol@legal.com']);
}

main().catch(console.error);

How It Works

1. Signing

When a document is signed, a SHA-256 hash of its content is computed and recorded as a fact via PacSpace. The content hash is the receipt — share it with all parties.

2. Verification

To verify, recompute the hash of the current document and compare it against the original recorded in PacSpace. If the hashes match and the fact is verified, the document is authentic.

3. Tamper Detection

If someone modifies the document after signing, the recomputed hash won't match the original — instantly exposing the alteration.

4. Multi-Party Signing

Multiple signers each record their own fact. A summary fact ties all signatures together, proving that all parties signed the same document.


Expected Output

=== Signing Document ===

Document signed successfully
  Document:     Service Agreement — Acme Corp
  Signer:       alice@acme.com
  Content Hash: sha256_a1b2c3...

=== Verifying Original Document ===

  VERIFIED — Document is authentic and untampered
  Signed by:    alice@acme.com
  Signed at:    2025-06-01T12:00:00.000Z
  Verified at:  2025-06-01T12:00:15.000Z

=== Verifying Tampered Document ===

  VERIFICATION FAILED — Document has been tampered with!
  Original hash: a1b2c3d4e5f6...
  Current hash:  x9y8z7w6v5u4...

=== Multi-Party Signing ===

Document signed successfully
  Document:     Service Agreement — Acme Corp
  Signer:       alice@acme.com
  Content Hash: sha256_abc...
Document signed successfully
  Document:     Service Agreement — Acme Corp
  Signer:       bob@partner.com
  Content Hash: sha256_def...
Document signed successfully
  Document:     Service Agreement — Acme Corp
  Signer:       carol@legal.com
  Content Hash: sha256_ghi...

All 3 parties signed. Summary hash: sha256_summary...

Next Steps

Was this page helpful?

Last updated February 11, 2026