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
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
- See Facts & Audit Trails for the full Facts API reference
- Review Error Handling for retry strategies
- Set up webhooks to get notified when facts are verified
Last updated February 11, 2026