Building Pre-Payment Authorization

Technical Architecture for Payment Schemes

Back to all posts

Posted on January 22, 2026

 This article is part of a series on programmable authorization for corporate payments.

In the last post, I made the case for why mobile payment schemes should add pre-payment authorization approval to capture the corporate B2B market. The reaction I usually get:

"Cool idea, but how does this actually work? Don't payments authorize too fast for approval logic?"

This is the right question. Building a programmable authorization approval platform isn't just about calling APIs and hoping for the best. It's a distributed systems problem with specific constraints depending on your rails.

Let's walk through the technical architecture required to make this work in production—without breaking your existing payment infrastructure.

The Core Challenge: Adding Decision Points to Instant Payments

Traditional instant payment flow (without authorization):

  1. User initiates payment (scans QR, selects merchant in app)
  2. Issuer checks account balance
  3. Issuer approval forwarded back to merchant
  4. Done

The problem: There's no decision point where external business logic can run before the money moves.

The solution: Add a pre-payment authorization approval layer at the scheme level.

Authorization 101: Understanding the Baseline

Before we talk about programmability, let's establish how traditional "dumb pipe" flows work.

Simple. User initiates payment, scheme gets payment guarantee from issuer, payment guarantee forwarded to merchant, payment confirmed to user.

The limitation: All logic lives in the issuer's black box. When a corporate treasurer wants to issue company accounts with spending policies, there's no hook to enforce rules before the payment executes.

The scheme might offer basic daily limits, but custom logic like "only allow this during work hours" or "require manager approval over €500"? Not available.

Enter Sub-Account Architecture: Virtual Accounts with Independent Authorization

The solution is adding authorization routing logic at the scheme level before payment execution.

The Sub-Account Model

Instead of giving each employee a separately-funded account or wallet, implement scheme-level virtual sub-accounts that route to corporate main accounts:

Corporate account structure:

  1. Main corporate bank account (or multiple accounts for different purposes)
    • Holds all corporate funds
    • Under corporate treasury control
    • Can earn yield, be part of cash management strategy
  2. Scheme-level sub-accounts (virtual/logical, not actual bank accounts)
    • Each employee gets a sub-account identifier
    • Sub-accounts have independent authorization rules
    • All payments from any sub-account ultimately settle from the designated main account
    • Sub-accounts don't hold balances—they're routing and policy constructs

How this works in practice:

Key differences from traditional instant payments:

  1. Scheme-level routing decision: The payment scheme identifies this as a corporate sub-account and pauses before executing the payment
  2. External authorization logic: Corporate policy API is called with transaction context (employee, amount, merchant category, time)
  3. Dynamic settlement source: Corporate policy can specify which main account to settle from (e.g., EUR account, project-specific account, crypto liquidation)
  4. No pre-funding required: Employee's sub-account doesn't need a balance—authorization determines if payment should be allowed, then funds come from main account

Benefits over traditional pre-funded wallets:

  • Capital efficiency: All money stays in corporate main account(s) where it can earn yield
  • Treasury control: Finance team has single-account (or few accounts) visibility
  • Fraud prevention: Compromised employee credentials can't drain a pre-funded balance; each transaction requires authorization
  • Policy flexibility: Each sub-account can have completely different authorization rules
  • Simplified reconciliation: All debits from main account(s), each tagged with employee/category metadata

Why This Works Better Than Real-Time Funding

Some platforms attempt "Just-In-Time (JIT) funding" where they move money from a master account to an individual account right before authorization. This creates technical challenges:

JIT funding problems:

  • Must move funds fast enough that the account has balance when authorization checks it
  • Creates timing dependencies (what if transfer hasn't completed when authorization arrives?)
  • Requires actual account-to-account transfers, adding latency and complexity
  • May require coordination with external banking systems

Sub-account model advantages:

  • No account-to-account transfers needed before payment
  • Authorization logic runs first, then payment executes from correct source
  • Simpler architecture: scheme-level routing, not real-time funding
  • JIT may not work if corporate main account is at a different bank (scheme handles the settlement routing)

Context-Aware Authorization: What Data Do You Need?

For corporate spending policies to work, the authorization approval flow needs contextual information. But you don't need detailed line-item data—basic transaction metadata is sufficient for most policies.

Basic Transaction Context (Privacy-Preserving)

What the corporate policy API receives:

  
  
    
{
  "sub_account_id": "employee_42",
  "amount": 12000,
  "currency": "EUR",
  "merchant": {
    "name": "Restaurant ABC",
    "category": "5812",
    "location": "Munich, Germany"
  },
  "timestamp": "2025-01-15T12:34:56Z",
  "transaction_id": "tx_abc123"
}

    
  

What's NOT included (privacy-preserving):

  • Detailed line items (specific menu items purchased)
  • Full transaction receipt
  • Other customers' data at the merchant
  • Personal information beyond what's necessary for authorization

This is enough for most corporate policies:

Time-based policies:

  
  
    
# Decline meal purchases outside work hours
if merchant.category == "5812":  # Restaurant
    if is_weekend(timestamp) or not is_work_hours(timestamp):
        return DECLINE("Meals only during work hours")

    
  

Category-based policies:

  
  
    
# Fuel cards only at fuel merchants
if sub_account.type == "fuel_card":
    if merchant.category != "5541":  # Service stations
        return DECLINE("This card is for fuel purchases only")

    
  

Budget-based policies:

  
  
    
# Check remaining budget for category
budget_remaining = get_category_budget("meals", employee_id)
if amount > budget_remaining:
    return DECLINE("Meals budget exceeded")

    
  

Location-based policies:

  
  
    
# Restrict to specific geographic areas
employee_office = get_employee_office(employee_id)
if distance(merchant.location, employee_office) > 50km:
    return DECLINE("Transaction outside approved area")

    
  

Value: These policies cover 80%+ of corporate use cases without requiring detailed receipt data or invasive tracking.

When More Context Helps (Optional)

For specific industries, additional context can be valuable—but only if the merchant provides it and the scheme chooses to pass it through:

Fleet/logistics:

  • Odometer reading (if merchant terminal supports it)
  • Vehicle ID (from company fleet system)
  • Driver shift status (from HR/scheduling system)

Travel/hospitality:

  • Booking reference (for hotel/airline purchases)
  • Trip purpose (from expense system)
  • Attendees (for group meals)

Important: This additional context typically comes from corporate's own systems (fleet management, HR, expense tools), not from the merchant. The scheme's job is to provide the authorization hook—corporates decide what data to check.

Intelligent Routing: Multi-Source Settlement

One powerful capability of the sub-account model: dynamic routing to different funding sources based on corporate policy.

Currency-Based Routing

For companies operating across multiple markets, the corporate logic can instruct the payment scheme which funding source should be used for a given payment. This allows companies to easily route payments to a corporate account in the matching currency.

Value: Automated currency management, no manual transfers needed, optimized FX costs.

Project/Cost-Center Routing

For companies with complex accounting:

  
  
    
def route_payment(employee, amount, merchant):
    # Determine which project/cost center
    active_project = get_active_project(employee)

    if active_project:
        # Route to project-specific account
        return {
            "approved": True,
            "settle_from": f"account_project_{active_project}",
            "metadata": {"project": active_project,
                         "cost_center": get_cost_center(active_project)}
        }
    else:
        # Route to general corporate account
        return {
            "approved": True,
            "settle_from": "account_general",
            "metadata": {"cost_center": "general"}
        }

    
  

Value: Automatic cost center assignment, simplified accounting, real-time project expense tracking.

The Latency Budget: How Fast Does This Need to Be?

Unlike card networks (which have hard 2000ms timeouts), instant payment schemes control the user experience. But you still need to be reasonably fast or users abandon transactions.

Key differences from card networks:

  1. No external hard timeout: You control the entire flow, so you can take 2-3 seconds for corporate logic without automatic decline
  2. User expectation management: Show "Processing payment..." in the app—users understand instant payments take a few seconds
  3. Progressive feedback: Can show "Authorization approved, processing payment..." so user knows it's working

Error Handling & Fallback Logic

Networks fail. APIs go down. Your authorization system must gracefully handle failures.

Fallback Policies

When the corporate policy API is unreachable:

Option 1: Fail-safe (decline all)

  
  
    
try:
    decision = call_corporate_api(payload)
except (Timeout, ConnectionError):
    return {"approved": False, "reason": "Corporate API unavailable"}

    
  
  • Safest for corporate (no unauthorized spend)
  • Worst UX (employees can't make purchases)

Option 2: Low-value auto-approve

  
  
    
try:
    decision = call_corporate_api(payload)
except (Timeout, ConnectionError):
    if payload.amount <= 5000:  # €50 threshold
        return {"approved": True, "reason": "Auto-approved (API unavailable)"}
    else:
        return {"approved": False,
                "reason": "High-value declined (API unavailable)"}

    
  
  • Balanced approach
  • Employees can make small purchases
  • Large purchases blocked until API recovers

Option 3: Historical pattern matching

  
  
    
try:
    decision = call_corporate_api(payload)
except (Timeout, ConnectionError):
    # Check if employee has made similar purchases before
    similar_approved = db.query(
        f"SELECT COUNT(*) FROM transactions "
        f"WHERE employee_id = {employee_id} "
        f"AND merchant_category = {category} "
        f"AND status = 'approved' "
        f"AND timestamp > NOW() - INTERVAL '30 days'"
    )

    if similar_approved > 3:
        return {"approved": True, "reason": "Pattern-based approval"}
    else:
        return {"approved": False, "reason": "API unavailable, no pattern"}

    
  
  • Most sophisticated
  • Uses ML/heuristics to predict if transaction would be approved
  • Requires historical transaction data

Corporate configuration: Allow each corporate client to choose their fallback policy based on risk tolerance.

Circuit Breaker Pattern

Prevent cascading failures when corporate API is degraded:

  
  
    
from pybreaker import CircuitBreaker

# If 5 failures in 60 seconds, open circuit for 30 seconds
breaker = CircuitBreaker(fail_max=5, timeout_duration=30)

@breaker
def call_corporate_api(url, payload):
    return http_post(url, json=payload, timeout=2)

try:
    decision = call_corporate_api(corporate_webhook, payload)
except CircuitBreakerError:
    # Circuit is open, don't even try calling API
    decision = apply_fallback_policy(payload)

    
  

Why this matters: If corporate API is down, don't waste 2 seconds timing out on every request. Fail fast and apply fallback policy immediately.

Developer Experience: Making Integration Easy

The scheme that wins is the one with the best developer experience for corporate integrators.

You'll need to provide official SDKs in common corporate languages, as well as sandbox environments.

You'll also need to offer real-time visibility for corporate finance teams:

Metrics displayed:

  • Authorization response times (p50, p95, p99)
  • Approval vs. decline rates
  • Top decline reasons
  • Webhook endpoint health (uptime, error rates)
  • Budget consumption by category
  • Real-time spend by employee/department

Alerting:

  • Webhook endpoint down/slow
  • Unusual decline rate spike
  • Budget threshold approaching (e.g., 80% consumed)
  • Suspicious transaction patterns

This builds trust—corporates can see exactly how the system is performing and debug issues themselves.

Wrapping Up

The core insight isn't moving money differently, it's adding a decision point where none existed. Corporate policy logic runs before payment execution, using sub-accounts as routing constructs rather than funding vehicles.

What's surprising: this is primarily software development. No new payment rails, no new merchant agreements, no new banking licenses. The infrastructure you've already built for consumer payments carries you most of the way. 6-12 months of engineering gets you a working platform.

For corporate treasurers, this solves the fundamental objection: "I can't control spending before the money leaves." Now they can. Real-time policy enforcement, capital efficiency, full audit trail.

For payment schemes, it's a new revenue model with structural advantages: sticky API integrations, high switching costs, recurring platform fees instead of pure transaction volume.

But having the architecture doesn't guarantee you'll win. The corporate card incumbents have rebates, float, and entrenched relationships. Next, we'll explore where programmable authorization creates wedge opportunities—specific verticals where cards structurally fail and your solution is the only viable option.

 This article is part of a series on programmable authorization for corporate payments.