This guide provides step-by-step instructions for integrating the AffixIO Verification SDK into various payment platforms and agentic workflows.
- Express.js / Node.js REST API
- AWS Lambda + API Gateway
- Payment Processor (Stripe-like)
- Agentic Commerce Platform
- Compliance & Audit
npm install express @affix-io/verification-sdk// middleware/affixio.ts
import { Request, Response, NextFunction } from 'express';
import {
AffixioClient,
AffixioValidationError,
AffixioApiError,
} from '@affix-io/verification-sdk';
export const affixioClient = AffixioClient.fromEnv();
export interface VerificationRequest extends Request {
verificationResult?: {
eligible: boolean;
proof: string;
circuitId: string;
};
}
export async function requireIssuerAuth(
req: VerificationRequest,
res: Response,
next: NextFunction
) {
try {
const { agentId, accountId, consentRef, amount, currency, merchantCat } =
req.body;
const result = await affixioClient.verifyIssuerAgentAuthorization({
agentId,
accountId,
consentReference: consentRef,
amount,
currency,
merchantCategory: merchantCat,
});
if (!result.eligible) {
return res.status(403).json({
error: 'Agent not authorized',
circuitId: result.circuit_id,
proof: result.proof,
});
}
req.verificationResult = {
eligible: result.eligible,
proof: result.proof,
circuitId: result.circuit_id,
};
next();
} catch (error) {
if (error instanceof AffixioValidationError) {
return res.status(400).json({
error: 'Invalid verification request',
field: error.field,
});
}
if (error instanceof AffixioApiError) {
return res.status(502).json({
error: 'Verification service error',
requestId: error.requestId,
});
}
return res.status(500).json({ error: 'Internal server error' });
}
}// routes/payments.ts
import express from 'express';
import { requireIssuerAuth } from '../middleware/affixio.js';
const router = express.Router();
router.post('/authorize-payment', requireIssuerAuth, async (req, res) => {
const { agentId, amount } = req.body;
const { proof, circuitId } = req.verificationResult!;
// Save transaction with proof
const tx = await saveTransaction({
agentId,
amount,
verificationProof: proof,
circuitId,
timestamp: new Date(),
});
res.json({
status: 'authorized',
transactionId: tx.id,
proof,
});
});
export default router;npm install @affix-io/verification-sdk// handlers/verifyPayment.ts
import {
APIGatewayProxyHandler,
APIGatewayProxyResult,
} from 'aws-lambda';
import {
AffixioClient,
AffixioApiError,
AffixioTransportError,
} from '@affix-io/verification-sdk';
const client = AffixioClient.fromEnv();
export const handler: APIGatewayProxyHandler = async (
event
): Promise<APIGatewayProxyResult> => {
try {
const body = JSON.parse(event.body || '{}');
const {
agentId,
accountId,
consentRef,
amount,
currency,
merchantCategory,
} = body;
// Verify with AffixIO
const result = await client.verifyIssuerAgentAuthorization({
agentId,
accountId,
consentReference: consentRef,
amount,
currency,
merchantCategory,
});
if (!result.eligible) {
return {
statusCode: 403,
body: JSON.stringify({
error: 'Not eligible',
proof: result.proof,
}),
};
}
// Log to DynamoDB for audit trail
await dynamoDb.putItem({
TableName: 'VerificationAudit',
Item: {
requestId: event.requestContext.requestId,
agentId,
eligible: result.eligible,
proof: result.proof,
timestamp: new Date().toISOString(),
},
});
return {
statusCode: 200,
body: JSON.stringify({
eligible: result.eligible,
proof: result.proof,
latency: result.latency_ms,
}),
};
} catch (error) {
console.error('Error:', error);
if (error instanceof AffixioTransportError) {
// Transient error, Lambda will retry
return {
statusCode: 503,
body: JSON.stringify({ error: 'Service temporarily unavailable' }),
};
}
if (error instanceof AffixioApiError) {
// Log for investigation
console.error('AffixIO API error', {
status: error.status,
requestId: error.requestId,
});
return {
statusCode: 502,
body: JSON.stringify({
error: 'Verification service error',
requestId: error.requestId,
}),
};
}
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal server error' }),
};
}
};# Build
npm run build
# Deploy with CloudFormation or Serverless Framework
serverless deploy// services/paymentProcessor.ts
import {
AffixioClient,
AffixioApiError,
AffixioTransportError,
} from '@affix-io/verification-sdk';
interface PaymentRequest {
agentId: string;
accountId: string;
customerId: string;
amount: number;
currency: string;
merchantId: string;
merchantCategory: string;
consentReference: string;
}
interface PaymentResult {
status: 'approved' | 'declined' | 'error';
transactionId?: string;
verificationProof?: string;
reason?: string;
}
const affixioClient = AffixioClient.fromEnv();
export async function authorizeAgentPayment(
req: PaymentRequest
): Promise<PaymentResult> {
const startTime = Date.now();
try {
// Step 1: Verify issuer authorization
const issuerAuthResult = await affixioClient.verifyIssuerAgentAuthorization({
agentId: req.agentId,
accountId: req.accountId,
consentReference: req.consentReference,
amount: req.amount,
currency: req.currency,
merchantCategory: req.merchantCategory,
merchantId: req.merchantId,
});
if (!issuerAuthResult.eligible) {
await logPaymentAttempt({
...req,
status: 'declined',
reason: 'issuer_auth_failed',
proof: issuerAuthResult.proof,
duration: Date.now() - startTime,
});
return {
status: 'declined',
reason: 'Agent not authorized by issuer',
verificationProof: issuerAuthResult.proof,
};
}
// Step 2: Check merchant eligibility
const merchantCheckResult = await affixioClient.verifyMerchantAgentCheckout({
agentId: req.agentId,
customerId: req.customerId,
basketTotalCents: req.amount,
currency: req.currency,
merchantId: req.merchantId,
merchantCategory: req.merchantCategory,
});
if (!merchantCheckResult.eligible) {
await logPaymentAttempt({
...req,
status: 'declined',
reason: 'merchant_check_failed',
proof: merchantCheckResult.proof,
duration: Date.now() - startTime,
});
return {
status: 'declined',
reason: 'Merchant checkout not eligible',
verificationProof: merchantCheckResult.proof,
};
}
// Step 3: Process payment with Stripe / payment processor
const txId = await chargePaymentProcessor(req);
await logPaymentAttempt({
...req,
status: 'approved',
transactionId: txId,
issuerProof: issuerAuthResult.proof,
merchantProof: merchantCheckResult.proof,
duration: Date.now() - startTime,
});
return {
status: 'approved',
transactionId: txId,
verificationProof: issuerAuthResult.proof,
};
} catch (error) {
console.error('Payment authorization error:', error);
if (error instanceof AffixioTransportError) {
// Network issue; may retry or fallback
await logPaymentAttempt({
...req,
status: 'error',
reason: 'verification_network_error',
error: error.message,
duration: Date.now() - startTime,
});
return {
status: 'error',
reason: 'Verification service unavailable; please retry',
};
}
if (error instanceof AffixioApiError) {
// API error; log for investigation
console.error('AffixIO API error', {
status: error.status,
requestId: error.requestId,
});
return {
status: 'error',
reason: `Verification error (${error.status})`,
};
}
return {
status: 'error',
reason: 'Internal error; please retry',
};
}
}
async function chargePaymentProcessor(req: PaymentRequest): Promise<string> {
// Call Stripe / payment provider
// ...
return 'txn_12345';
}
async function logPaymentAttempt(data: Record<string, unknown>) {
// Log to database / analytics
console.log('[Payment Audit]', JSON.stringify(data));
}// services/agentCommerceVerification.ts
import {
AffixioClient,
AgentPaymentCompositeResponse,
} from '@affix-io/verification-sdk';
interface OrderVerificationRequest {
agentId: string;
accountId: string;
consentReference: string;
orderItems: Array<{
sku: string;
quantity: number;
priceCents: number;
category: string;
}>;
merchantId: string;
shippingCountry: string;
totalCents: number;
currency: string;
}
interface OrderDecision {
approved: boolean;
verificationDetails: AgentPaymentCompositeResponse;
recommendation: 'approve' | 'review' | 'decline';
reason?: string;
}
const affixioClient = AffixioClient.fromEnv();
export async function verifyAgentOrder(
req: OrderVerificationRequest
): Promise<OrderDecision> {
// For high-value orders, run composite verification
const isHighValue = req.totalCents > 50000; // $500+
const circuits = isHighValue
? [
'agentic-payment-permission',
'finance-account-standing',
'finance-fraud-indicator',
]
: ['agentic-payment-permission'];
const result = await affixioClient.verifyAgentPaymentComposite({
agentId: req.agentId,
accountId: req.accountId,
consentReference: req.consentReference,
transactionContext: {
amount_cents: req.totalCents,
currency: req.currency,
merchant_category: determineMerchantCategory(req.orderItems),
merchant_id: req.merchantId,
order_items_count: req.orderItems.length,
shipping_country: req.shippingCountry,
is_high_value: isHighValue,
},
circuits,
});
// Decide on order
let recommendation: 'approve' | 'review' | 'decline' = 'approve';
let reason: string | undefined;
if (!result.overallEligible) {
const failedCircuits = result.perCircuit.filter((c) => !c.eligible);
recommendation = 'decline';
reason = `Failed circuits: ${failedCircuits.map((c) => c.circuitId).join(', ')}`;
} else if (isHighValue && result.perCircuit.length < 3) {
// High-value but only partial verification
recommendation = 'review';
reason = 'High-value order with incomplete verification';
}
await logOrderVerification({
agentId: req.agentId,
totalCents: req.totalCents,
recommendation,
verificationProof: result.aggregatedProof,
perCircuit: result.perCircuit,
timestamp: new Date().toISOString(),
});
return {
approved: result.overallEligible,
verificationDetails: result,
recommendation,
reason,
};
}
function determineMerchantCategory(
items: Array<{ category: string }>
): string {
// Infer primary category from items
const categories = items.map((i) => i.category);
const primary = categories[0] || 'general_merchandise';
return primary;
}
async function logOrderVerification(data: Record<string, unknown>) {
console.log('[Order Verification]', JSON.stringify(data));
// Store in audit log / database
}// services/auditTrail.ts
import { VerifyResponse } from '@affix-io/verification-sdk';
interface AuditRecord {
id: string;
timestamp: Date;
agentId: string;
circuitId: string;
eligible: boolean;
proof: string;
requestMetadata?: Record<string, unknown>;
transactionId?: string;
}
export class AuditTrail {
async recordVerification(
tx: VerifyResponse,
agentId: string,
metadata?: Record<string, unknown>
): Promise<AuditRecord> {
const record: AuditRecord = {
id: generateAuditId(),
timestamp: new Date(),
agentId,
circuitId: tx.circuit_id,
eligible: tx.eligible,
proof: tx.proof,
requestMetadata: metadata,
};
// Store in immutable log (e.g., database, S3 with versioning)
await this.store(record);
return record;
}
async verifyProof(auditId: string, proof: string): Promise<boolean> {
const record = await this.retrieve(auditId);
return record && record.proof === proof;
}
async getAuditTrail(
agentId: string,
startDate: Date,
endDate: Date
): Promise<AuditRecord[]> {
return this.query({
agentId,
timestamp: { $gte: startDate, $lte: endDate },
});
}
private async store(record: AuditRecord): Promise<void> {
// TODO: Implement database storage
// e.g., PostgreSQL INSERT, DynamoDB PutItem, etc.
console.log('[Audit]', JSON.stringify(record));
}
private async retrieve(id: string): Promise<AuditRecord | null> {
// TODO: Implement database retrieval
return null;
}
private async query(
filter: Record<string, unknown>
): Promise<AuditRecord[]> {
// TODO: Implement database query
return [];
}
}
function generateAuditId(): string {
return `audit_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
}// services/complianceReport.ts
import { AuditTrail } from './auditTrail.js';
export async function generateComplianceReport(
startDate: Date,
endDate: Date
) {
const auditTrail = new AuditTrail();
// Collect all verification records
const records = await auditTrail.getAuditTrail('all-agents', startDate, endDate);
const summary = {
period: {
start: startDate.toISOString(),
end: endDate.toISOString(),
},
totalVerifications: records.length,
eligible: records.filter((r) => r.eligible).length,
declined: records.filter((r) => !r.eligible).length,
byCircuit: groupByCircuit(records),
byAgent: groupByAgent(records),
allProofsPresent: records.every((r) => r.proof),
};
return summary;
}
function groupByCircuit(records: Array<{ circuitId: string; eligible: boolean }>) {
const groups: Record<string, { total: number; eligible: number }> = {};
records.forEach((r) => {
if (!groups[r.circuitId]) {
groups[r.circuitId] = { total: 0, eligible: 0 };
}
groups[r.circuitId].total += 1;
if (r.eligible) {
groups[r.circuitId].eligible += 1;
}
});
return groups;
}
function groupByAgent(records: Array<{ agentId: string; eligible: boolean }>) {
const groups: Record<string, { total: number; eligible: number }> = {};
records.forEach((r) => {
if (!groups[r.agentId]) {
groups[r.agentId] = { total: 0, eligible: 0 };
}
groups[r.agentId].total += 1;
if (r.eligible) {
groups[r.agentId].eligible += 1;
}
});
return groups;
}// utils/retryStrategy.ts
import {
AffixioTransportError,
AffixioApiError,
AffixioClient,
} from '@affix-io/verification-sdk';
export interface RetryOptions {
maxAttempts: number;
initialDelayMs: number;
maxDelayMs: number;
backoffMultiplier: number;
}
const defaultRetryOptions: RetryOptions = {
maxAttempts: 3,
initialDelayMs: 100,
maxDelayMs: 5000,
backoffMultiplier: 2,
};
export async function withRetry<T>(
fn: () => Promise<T>,
options: RetryOptions = defaultRetryOptions
): Promise<T> {
let lastError: Error | undefined;
let delay = options.initialDelayMs;
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
// Check if error is retryable
if (!isRetryable(error)) {
throw error;
}
if (attempt < options.maxAttempts) {
console.warn(
`Attempt ${attempt} failed, retrying in ${delay}ms...`,
lastError.message
);
await new Promise((resolve) => setTimeout(resolve, delay));
delay = Math.min(delay * options.backoffMultiplier, options.maxDelayMs);
}
}
}
throw lastError || new Error('Max retries exceeded');
}
function isRetryable(error: unknown): boolean {
if (error instanceof AffixioTransportError) {
return true; // Network errors are retryable
}
if (error instanceof AffixioApiError) {
// Retry 5xx errors
return error.status >= 500;
}
return false;
}
// Usage:
const client = AffixioClient.fromEnv();
await withRetry(
() =>
client.verifyIssuerAgentAuthorization({
agentId: 'agent:001',
accountId: 'acct:123',
consentReference: 'consent:abc',
amount: 1000,
currency: 'USD',
merchantCategory: 'software_subscription',
}),
{
maxAttempts: 3,
initialDelayMs: 100,
maxDelayMs: 5000,
}
);The SDK integrates seamlessly into:
- REST APIs: Middleware for Express, etc.
- Serverless: AWS Lambda, etc.
- Payment systems: Authorization flows with Stripe, etc.
- Agentic platforms: Multi-circuit verification for complex decisions
- Compliance: Audit trails and reporting
Always:
- Log proofs for audit trails
- Distinguish between retryable (transport) and non-retryable (validation) errors
- Implement proper timeout and retry strategies
- Store verification results with transaction records