The gap in every payments conversation
Every embedded payments conversation covers program model, vendor selection, and compliance framework. Almost none of them cover the payment initiation flow — the actual mechanics of how a trigger event in a SaaS application becomes a completed financial transaction in the real world.
That gap is where most SaaS payment builds go wrong. Not in the program model decision. Not in the vendor selection. In the architecture of the flow itself: the trigger layer, the authorization gate, the rail selection logic, the instruction construction, the response handling, the exception path, and the reconciliation record. These are the eight components of a complete payment initiation architecture. Most SaaS implementations get two or three of them right and discover the others are missing when they break at scale.
Component 1: The trigger layer
A payment is initiated by something. In an AP automation system, it is an invoice approval event. In a marketplace, it is an order completion. In a lending platform, it is a disbursement schedule. In an AI agent, it is a policy-rule evaluation. The trigger is the event that connects the SaaS application logic to the payment execution layer.
Most SaaS systems have implicit triggers — the payment "just happens" when a certain state is reached in the application. Implicit triggers work in the happy path. They break when the trigger fires twice (duplicate payment), fires in an unexpected state (payment before the counterparty is verified), or fails to fire when it should (payment not initiated because a state transition was missed).
Explicit trigger design means: defining the exact application state that initiates a payment, the inputs the trigger passes to the payment flow (payee identifier, amount, currency, rail preference, reference data), the idempotency key that prevents duplicate triggers, and the audit record of every trigger event regardless of outcome. This is not complex — it is deliberate. Implicit triggers produce implicit bugs at scale.
Component 2: The authorization gate
Before any payment instruction goes to a payment partner, there should be an explicit gate that answers: is this payment authorized to execute right now? The gate checks: is the payment within policy (amount within limit, payee on approved list, rail within permitted options)? Has the payment cleared the duplicate check (is there already a payment in flight or settled for this reference)? Has the payee cleared OFAC screening? Are all required fields present and valid?
The authorization gate is frequently missing in SaaS payment implementations. The payment goes directly from trigger to instruction. This means the program discovers authorization failures at the payment partner level — where they are more expensive to handle — rather than at the gate level — where they are cheap to catch and log.
A gate rejection is a clean exception: the payment didn't initiate, the trigger state is unchanged, the exception is logged with the gate failure reason. A payment partner rejection after instruction is a messier exception: the payment was partially initiated, state may have changed, and recovery requires understanding where in the partner's system the failure occurred.
Component 3: Rail selection
For programs with multiple rails available — VCard, ACH, same-day ACH, RTP/FedNow, wire, check — which rail does this specific payment use? The rail selection logic sits between the authorization gate and the payment instruction. It is the waterfall / orchestration decision.
Rail selection inputs: payee rail capabilities (is the payee enrolled for VCard? ACH-only? Wire capable?), payment amount (wire threshold, VCard acceptance probability by amount range), urgency (RTP for time-sensitive, ACH for standard), monetization priority (VCard before ACH for monetization-optimized programs), and fallback logic (if primary rail fails, what is the next attempt?).
The rail selection logic is not provided by the payment partner. It is designed by the program operator — or, if it is not explicitly designed, it defaults to whatever the payment partner's API defaults to, which is typically the cheapest or most available rail rather than the one that serves the program's requirements.
Rail selection should be implemented as a named function with explicit inputs and outputs — not embedded in the payment initiation logic. A named function is testable, loggable, and replaceable when the waterfall logic needs to change. Embedded logic is none of those things.
Component 4: Payment instruction construction
The payment instruction is the structured data object sent to the payment partner to initiate the payment. It contains: the payee identifier (bank account, card number, or partner-specific identifier), the amount and currency, the payment reference and remittance data, the rail-specific fields (ACH SEC code, wire purpose, VCard spending controls), and the Level 2/3 data for eligible B2B card transactions.
The instruction construction is where most of the business requirements that don't appear in API documentation live. The remittance data structure that allows a supplier to auto-reconcile a payment is not in the API documentation — it is in the operator's knowledge of what the supplier's AR system expects. The Level 2/3 data fields that reduce interchange on B2B card payments require configuration that the API will accept but not require. The spending controls on a VCard that are the compliance architecture for an AI-executed payment require explicit design, not API defaults.
Document the instruction construction logic in detail — every field, its source, its validation rule, and its business purpose. This documentation is the most valuable artifact in the payment architecture for debugging, compliance review, and onboarding new engineers.
Component 5: Partner API call and response handling
The payment instruction is sent to the payment partner's API. The partner returns a response — synchronous for card authorization, asynchronous for ACH, near-real-time for RTP. The SaaS application needs to handle each response type correctly.
Synchronous responses (card authorization): update application state and ledger immediately on success, handle declines and errors with specific logic per decline code. Asynchronous responses (ACH, wire): update application state to "pending", register for the partner's webhook or polling endpoint, handle the eventual settlement or return notification. Near-real-time responses (RTP/FedNow): typically synchronous from the application's perspective but the final settlement confirmation may be asynchronous.
Most implementations handle the success case correctly and the failure case inconsistently. At scale, the failure case is where most operations overhead lives. Every failure code should have a defined handling path: is it retryable? What is the retry logic? Does it require application state rollback? Does it trigger a notification to operations?
Component 6: Application state and ledger update
When does the payment count as complete in your application? The answer differs by rail: card authorization is an approval but not settlement; ACH settles 1–3 days later; RTP settles in seconds. The application state update and the ledger entry need to reflect the settlement state correctly — not the authorization state.
Most SaaS payment implementations update application state at authorization and discover the settlement lag when reconciliation doesn't match. The correct design: separate application states for authorized, settled, and failed, with the ledger update triggered by settlement confirmation rather than authorization. This requires handling the asynchronous settlement notification — which is where the ledger entry actually belongs.
Component 7: The exception path
What happens when the payment fails at any point in the flow? The exception path is the part of the payment architecture most SaaS builds leave incomplete — because it is not in the happy path, is not required for a demo, and is not visible until payments are failing at volume.
A complete exception path has five elements: failure classification (what type of failure, by component and error code), notification (who is notified, how fast, through what channel), resolution workflow (what action resolves this failure type — retry, manual intervention, cancellation), retry logic (when and how many times to retry, with what backoff), and escalation (when does the exception become a human problem that exits the automated flow).
Missing exception paths mean exceptions accumulate in a queue that grows until it is manually processed. At $10M monthly with even a 1% exception rate, 100 unhandled exceptions per day accumulates to a 2,000-item manual queue in three weeks. Build the exception path before you need it — not after the queue exists.
Component 8: The reconciliation record
Every payment should produce a reconciliation record at execution time — the structured data that allows your ledger to be reconciled to the bank statement and the payment partner's settlement report daily. This record should contain: the internal payment identifier, the payment partner's transaction reference, the amount, the rail, the settlement date, the payee identifier, and the authorization trace.
Reconciliation records designed as afterthoughts produce daily reconciliation exceptions that grow with volume. Programs that design the reconciliation record structure at architecture time — as a defined output of the payment flow rather than a report extracted from logs — produce clean daily reconciliation with minimal manual intervention at any scale.
Putting the flow together
The complete payment initiation flow: trigger event → authorization gate (policy, duplicate, OFAC) → rail selection (waterfall logic) → instruction construction (with all required fields and metadata) → partner API call → response handling (by rail and response type) → state and ledger update (at settlement, not authorization) → reconciliation record. Eight components, each with defined inputs, outputs, failure modes, and exception handling.
The difference between a payment program that works at launch and one that scales cleanly is whether all eight components were designed before the first line of integration code was written. The ones designed before build are 20-line functions with clear interfaces. The ones designed after build are 200-line patches around the edges of code that wasn't designed to handle them.
Designing your payment initiation architecture? See how ExpandUp approaches infrastructure stack design, or talk with us before you build.