Skip to content

Implement Async Payment Settlement with Status Endpoint #504

@djwhitt

Description

@djwhitt

Summary

Enable asynchronous payment settlement after data streaming completes to eliminate settlement latency from the critical path. Provide settlement status via a dedicated endpoint instead of blocking the response.

Context

Currently, x402 payment settlement happens synchronously before streaming data (see src/middleware/x402.ts:417-434). This blocks the response for up to 5 seconds (configurable via X_402_USDC_SETTLE_TIMEOUT_MS) while waiting for on-chain settlement confirmation.

The X-Payment-Response header contains base64-encoded SettleResponse:

{
  success: boolean;              // Whether settlement succeeded
  transaction: string;           // On-chain transaction hash
  network: string;              // Network name (e.g., "base-sepolia")
  payer?: string;               // Payer's address
  errorReason?: string;         // Error reason if failed
}

This information is valuable for clients to verify payments on-chain, but requiring it before streaming adds significant latency.

Problem

Blocking settlement creates poor user experience:

  • Users wait 0-5 seconds before data starts streaming
  • Settlement timeout is the critical path for every paid request
  • Network congestion or facilitator delays directly impact response time
  • Cannot stream data until settlement completes

Proposed Solution

Architecture

  1. Settlement ID Header - Return before streaming starts:

    X-Payment-Settlement-Id: <uuid>
    X-Payment-Settlement-Status-Url: /ar-io/x402/settlement/<uuid>
    
  2. Immediate Streaming - Start streaming data immediately after verification

  3. Background Settlement - Settle payment after streaming completes:

    res.on('finish', async () => {
      const result = await paymentProcessor.settlePayment({...});
      await db.storeSettlementResult(settlementId, result);
    });
  4. Status Endpoint - Clients can check settlement status:

    GET /ar-io/x402/settlement/:id
    
    Response:
    {
      settlementId: string;
      status: 'pending' | 'completed' | 'failed';
      result?: {
        success: boolean;
        transaction: string;
        network: string;
        payer?: string;
        errorReason?: string;
      };
      createdAt: string;
      completedAt?: string;
    }

Database Schema

CREATE TABLE IF NOT EXISTS payment_settlements (
  settlement_id TEXT PRIMARY KEY,
  payment_payload TEXT NOT NULL,
  payment_requirements TEXT NOT NULL,
  status TEXT NOT NULL CHECK(status IN ('pending', 'completed', 'failed')),
  settlement_result TEXT,
  created_at INTEGER NOT NULL,
  completed_at INTEGER,
  INDEX idx_status_created ON payment_settlements(status, created_at)
);

Benefits

  • Zero settlement latency for users - data streams immediately
  • Same information available - clients can poll status endpoint
  • Better than headers - JSON response instead of base64-encoded string
  • Retry capability - clients can re-check status if needed
  • Failure tracking - can implement settlement penalties (see Refactor X402 Payment and Rate Limiting from Middleware to Handler-Based Architecture #503)
  • No timeout pressure - settlement can take as long as needed

Implementation Notes

  • Requires database for settlement tracking
  • Settlement status should expire after reasonable TTL (e.g., 24 hours)
  • Status endpoint should be rate-limited
  • Consider webhook callbacks for settlement completion (future enhancement)

Related Issues

Acceptance Criteria

  • Settlement ID generated and returned in headers before streaming
  • Payment settlement happens asynchronously after streaming completes
  • Status endpoint returns settlement results
  • Database schema supports settlement tracking
  • Settlement records expire after TTL
  • Status endpoint is rate-limited
  • E2E tests verify async settlement flow
  • Documentation updated with new flow

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions