Integration Examples
Autonomous Settlement Agent
Build an agent that listens to webhooks, validates the proof chain, detects discrepancies, and auto-escalates.
This example builds a fully autonomous settlement agent using Express.js. It listens to PacSpace webhooks, validates incoming proofs, checks for discrepancies against a local ledger, monitors SLA compliance, and auto-escalates when something is wrong.
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://balance-api.pacspace.io';
const SLA_MAX_LATENCY_MS = 30_000; // 30 seconds
const DISCREPANCY_THRESHOLD = 0.01; // 1% tolerance
// --- Local Ledger (in production, use a database) ---
const localLedger = new Map();
const proofChain = [];
// --- 1. Signature Verification ---
function verifySignature(payload, signature) {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest('hex');
return `sha256=${expected}` === signature;
}
// --- 2. Chain Continuity Check ---
function validateProofChain(event) {
const lastProof = proofChain[proofChain.length - 1];
if (lastProof && event.data.batchId) {
// Verify batch IDs are sequential and no gaps
const lastBatchNum = parseInt(lastProof.batchId.split('_')[1], 10);
const currentBatchNum = parseInt(event.data.batchId.split('_')[1], 10);
if (currentBatchNum <= lastBatchNum) {
return { valid: false, reason: 'Batch ID is not sequential — possible replay' };
}
}
proofChain.push({
batchId: event.data.batchId,
timestamp: event.timestamp,
hash: crypto.createHash('sha256').update(JSON.stringify(event.data)).digest('hex')
});
return { valid: true };
}
// --- 3. Discrepancy Detection ---
function checkDiscrepancy(recordKey, reportedBits) {
const localRecord = localLedger.get(recordKey);
if (!localRecord) {
console.warn(`No local record for ${recordKey} — 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 { creditsConsumed, remainingBalance } = event.data;
const warningThreshold = 1000;
if (remainingBalance < warningThreshold) {
escalate('low_balance', {
remainingBalance,
threshold: warningThreshold,
message: `Credit balance is low: ${remainingBalance} credits remaining`
});
}
return { creditsConsumed, remainingBalance };
}
// --- 6. Local Ledger Update ---
function updateLocalLedger(event) {
const items = event.data.items || [];
for (const item of items) {
const existing = localLedger.get(item.key) || { bits: 0, writes: 0 };
localLedger.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(`Settlement alert: ${type}`, details);
// await pagerduty.trigger(type, details);
}
// --- Webhook Handler ---
app.post('/webhooks/pacspace', (req, res) => {
const signature = req.headers['x-pacspace-signature'];
const event = req.body;
// Step 1: Verify signature
if (!verifySignature(event, signature)) {
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: Validate proof chain continuity
const chainResult = validateProofChain(event);
if (!chainResult.valid) {
escalate('chain_break', { reason: chainResult.reason, event });
return res.status(200).json({ received: true, warning: chainResult.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', { recordKey: 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,
batchId: event.data.batchId
});
}
}
// Step 5: Track credit usage
if (event.data.creditsConsumed !== undefined) {
trackUsage(event);
}
// Step 6: Update local ledger
updateLocalLedger(event);
res.status(200).json({ received: true });
});
// --- Health Check ---
app.get('/health', (req, res) => {
res.json({
status: 'ok',
ledgerSize: localLedger.size,
proofChainLength: proofChain.length,
lastProof: proofChain[proofChain.length - 1] || null
});
});
// --- Start Server ---
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Settlement 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. Chain Continuity
The agent tracks batch IDs and ensures they arrive in order. If a batch ID is out of sequence, it could indicate a replay attack or dropped events — triggering an escalation.
3. Discrepancy Detection
Each verified item is compared against the local ledger. 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 watches credit consumption from each event. When the remaining balance drops below a threshold, it triggers a low-balance alert.
6. Local Ledger
All verified events update a local ledger, keeping a running total per record key. 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 settlement-agent.js
Then configure a webhook in the PacSpace Dashboard pointing to https://your-server.com/webhooks/pacspace.
Next Steps
- Replace the in-memory ledger 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 & Pricing to understand credit consumption
Last updated February 11, 2026