Autonomous Recordation Agent
Build an agent that listens to webhooks, stores proofs, detects mismatches, and alerts an operator.
This example builds an agent using Express.js. It listens to PacSpace webhooks, stores proof fields, checks for mismatches against a local record store, and alerts an operator when something needs review.
Prerequisites
- Node.js 18+
- An Express.js server
- A configured webhook endpoint pointing to your server
- Your webhook secret (from webhook creation)
Full Example
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// --- Configuration ---
const WEBHOOK_SECRET = process.env.PACSPACE_WEBHOOK_SECRET;
const API_KEY = process.env.PACSPACE_API_KEY;
const API_BASE = 'https://app.pacspace.io';
const SLA_MAX_LATENCY_MS = 30_000; // 30 seconds
const DISCREPANCY_THRESHOLD = 0.01; // 1% tolerance
// --- Local record store (in production, use a database) ---
const localRecords = new Map();
const proofHistory = [];
// --- 1. Signature Verification ---
function verifySignature(rawBody, signature, timestamp) {
const signedContent = `${timestamp}.${rawBody}`;
const expected = 'v1=' + crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(signedContent)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
// --- 2. Proof Continuity Check ---
function validateProofContinuity(event) {
const lastProof = proofHistory[proofHistory.length - 1];
if (lastProof && event.data.proof?.previousProofHash) {
// Check continuity when previous proof fields are present
if (event.data.proof.previousProofHash !== lastProof.proofHash) {
return { valid: false, reason: 'Previous proof fingerprint does not match' };
}
}
proofHistory.push({
proofHash: event.data.proof?.proofHash,
timestamp: event.timestamp,
hash: crypto.createHash('sha256').update(JSON.stringify(event.data)).digest('hex')
});
return { valid: true };
}
// --- 3. Discrepancy Detection ---
function checkDiscrepancy(key, reportedBits) {
const localRecord = localRecords.get(key);
if (!localRecord) {
console.warn(`No local record for ${key} - first event or missing data`);
return { discrepancy: false, note: 'no_local_record' };
}
const diff = Math.abs(localRecord.bits - reportedBits);
const tolerance = localRecord.bits * DISCREPANCY_THRESHOLD;
if (diff > tolerance) {
return {
discrepancy: true,
localBits: localRecord.bits,
reportedBits,
difference: diff,
percentOff: ((diff / localRecord.bits) * 100).toFixed(2) + '%'
};
}
return { discrepancy: false };
}
// --- 4. SLA Monitoring ---
function checkSLA(event) {
const submittedAt = new Date(event.data.submittedAt).getTime();
const verifiedAt = new Date(event.data.verifiedAt).getTime();
const latency = verifiedAt - submittedAt;
return {
withinSLA: latency <= SLA_MAX_LATENCY_MS,
latencyMs: latency,
maxAllowedMs: SLA_MAX_LATENCY_MS
};
}
// --- 5. Usage Tracking ---
function trackUsage(event) {
const { periodId, consumedDeltas, remainingDeltas } = event.data.usage || {};
const warningThreshold = 1000;
if (remainingDeltas !== undefined && remainingDeltas < warningThreshold) {
escalate('low_usage', {
periodId,
remainingDeltas,
threshold: warningThreshold,
message: `Plan usage is high for ${periodId}: ${remainingDeltas} deltas remaining`
});
}
return { periodId, consumedDeltas, remainingDeltas };
}
// --- 6. Local Record Update ---
function updateLocalRecords(event) {
const items = event.data.items || [];
for (const item of items) {
const existing = localRecords.get(item.key) || { bits: 0, writes: 0 };
localRecords.set(item.key, {
bits: existing.bits + item.bits,
writes: existing.writes + 1,
lastUpdated: event.timestamp
});
}
}
// --- Escalation Handler ---
function escalate(type, details) {
console.error(`[ESCALATION] ${type}:`, JSON.stringify(details, null, 2));
// In production, send alerts via Slack, PagerDuty, email, etc.
// Example:
// await slack.send(`PacSpace alert: ${type}`, details);
// await pagerduty.trigger(type, details);
}
// --- Webhook Handler ---
app.post('/webhooks/pacspace', (req, res) => {
const signature = req.headers['x-pacspace-signature'];
const timestamp = req.headers['x-pacspace-timestamp'];
const event = req.body;
const rawBody = req.rawBody || JSON.stringify(event);
// Step 1: Verify signature
if (!verifySignature(rawBody, signature, timestamp)) {
console.error('Invalid webhook signature - rejecting');
return res.status(401).json({ error: 'Invalid signature' });
}
console.log(`Received event: ${event.event} at ${event.timestamp}`);
// Step 2: Check proof continuity
const continuityResult = validateProofContinuity(event);
if (!continuityResult.valid) {
escalate('proof_continuity_break', { reason: continuityResult.reason, event });
return res.status(200).json({ received: true, warning: continuityResult.reason });
}
// Step 3: Check for discrepancies
if (event.data.items) {
for (const item of event.data.items) {
const discResult = checkDiscrepancy(item.key, item.bits);
if (discResult.discrepancy) {
escalate('discrepancy', { key: item.key, ...discResult });
}
}
}
// Step 4: Monitor SLA compliance
if (event.data.submittedAt && event.data.verifiedAt) {
const slaResult = checkSLA(event);
if (!slaResult.withinSLA) {
escalate('sla_breach', {
latencyMs: slaResult.latencyMs,
maxAllowedMs: slaResult.maxAllowedMs,
receiptId: event.data.receiptId
});
}
}
// Step 5: Track plan usage
if (event.data.usage) {
trackUsage(event);
}
// Step 6: Update local records
updateLocalRecords(event);
res.status(200).json({ received: true });
});
// --- Health Check ---
app.get('/health', (req, res) => {
res.json({
status: 'ok',
recordCount: localRecords.size,
proofHistoryLength: proofHistory.length,
lastProof: proofHistory[proofHistory.length - 1] || null
});
});
// --- Start Server ---
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Recordation agent listening on port ${PORT}`);
});
How It Works
1. Signature Verification
Every incoming webhook is validated using HMAC-SHA256 with your webhook secret. Requests with invalid signatures are rejected immediately.
2. Proof Continuity
The agent tracks proof fingerprints and checks each new proof against the previous one when that field is present. If the value does not match, alert an operator.
3. Discrepancy Detection
Each verified item is compared against local records. If the reported value differs by more than the configured tolerance (1% by default), the agent escalates.
4. SLA Monitoring
The time between submission and verification is measured. If it exceeds the SLA threshold (30 seconds by default), the agent flags an SLA breach.
5. Usage Awareness
The agent tracks plan usage from each event. When the remaining deltas drop below a threshold, it triggers an alert so you can upgrade or manage overage.
6. Local Record Store
All verified events update local records, keeping a running total per record. In production, replace the in-memory Map with a database.
Running the Agent
# Set environment variables
export PACSPACE_WEBHOOK_SECRET="whsec_your_secret_here"
export PACSPACE_API_KEY="pk_live_PUBLIC.SECRET"
# Install dependencies
npm install express
# Start the agent
node recordation-agent.js
Then configure a webhook in the PacSpace Dashboard pointing to https://your-server.com/webhooks/pacspace.
See Also
- Agents Overview - the full guide for building agentic integrations on PacSpace.
- Integration Patterns - event-driven, periodic, and hybrid metering for agents.
- Safety and Idempotency - idempotency, retries, and cadence for agent workloads.
Next Steps
- Replace the in-memory record store with a persistent database (PostgreSQL, SQLite, etc.)
- Add Slack or PagerDuty integration for production escalations
- See SaaS Usage Metering for a billing-focused integration
- Review Billing Overview to understand plan usage