# The Payment State Machine: Every Status a Transaction Can Be In, and Who Can Change It

Canonical URL: https://y.uno/en/blog/the-payment-state-machine-every-status-a-transaction-can-be-in-and-who-can-change-it

> This is the markdown rendition for AI agents. The canonical page is served as HTML at the URL above.

By Yuno · Published 2026-10-30 · Payment orchestration

Most payment incidents are state bugs, not provider bugs. This masterclass teaches engineers and heads of payments how to model every transaction status, who owns each transition, and how to build webhook handling that survives retries, duplicates, and out-of-order delivery.

What you will learn: How to model a payment state machine, who is allowed to trigger each transition, which payment methods resolve asynchronously, and how to write webhook handling that survives retries, duplicates, and out-of-order events. After reading this, you can draw the full state diagram on a whiteboard and explain the design to any engineer or CFO.
Most payment incidents trace back to state bugs, not provider bugs. A provider timeout gets blamed; the real failure was code that moved an order to "complete" before a capture webhook arrived. Designing for asynchronous outcomes from day one is what makes adding the tenth payment method as cheap as adding the first. That is the thesis this piece argues, step by step.

## Key takeaways

- A payment passes through seven states: created, pending, authorized, captured, partially captured, voided, refunded, and disputed. Each state has a fixed set of allowed transitions.
- Only four actors can trigger transitions: the customer, the issuer, the merchant, and the payment provider. Confusing who owns a transition is the root of most state bugs.
- Refunds are only valid against captured transactions, not pending authorizations, according to Mastercard API Documentation.
- Authorization holds expire in five to eight days, per the Mastercard Integration Guide. Missing that window silently releases the funds.
- Webhook delivery is not guaranteed to arrive exactly once. Your endpoint must be idempotent before you add a second payment method, let alone a tenth.

## What a payment state machine is
A payment state machine is a formal model that defines every status a transaction can hold and the exact transitions allowed between them. It is not a flow chart; it is a contract between your system and every payment provider you connect.
Without this contract, engineers make local decisions at each integration point. Those decisions diverge. One integration marks an order fulfilled on authorization; another waits for capture. The state machine eliminates that ambiguity by forcing every provider&#x27;s events through a single normalized model.

## The seven states and what each one means
Each state represents a stable condition the transaction can rest in until an actor causes a transition. Terminal states accept no further transitions.

### Created
The payment intent exists in your system. No network request has succeeded yet. The customer may abandon at this point, leaving the record in created indefinitely. Set a TTL and expire it cleanly.

### Pending
The request left your system and reached the provider. A result has not arrived. This is the critical async state. ACH transfers, SEPA credits, PIX, and M-Pesa all rest here until the provider settles. According to the American Express ACH Transfers Document, ACH transfers take days to settle, while wire transfers take hours. Your order system must treat pending as a stable state, never a temporary one to race past.

### Authorized
The issuer approved the hold. Funds are reserved but not moved. The Mastercard Integration Guide states that funds are reserved for a period of time, in most cases five to eight days, as determined by the card scheme and the issuer&#x27;s rules. That window is your capture deadline. Build an expiry job that captures or voids before day five on any open authorization to stay safely inside the window.

### Captured and partially captured
The merchant instructed the provider to collect the funds. A full capture closes the authorization. A partial capture collects less. According to the Mastercard Integration Guide, when submitting a CAPTURE request you can provide an amount lower than the authorized amount. The uncaptured remainder is released to the cardholder. Ride-hailing and hospitality rely on this pattern daily: authorize the estimated fare, capture the actual one.

### Voided
The merchant cancelled the order before payment details reached the bank. The Mastercard Integration Guide is precise: use VOID to cancel immediately with the gateway. If details have already reached the bank, VOID is no longer available and REFUND must be used instead. Void is faster and creates no new money movement. It is a terminal state.

### Refunded
The merchant returned funds after capture. Mastercard API Documentation makes the sequencing explicit: a refund links to the capture or pay through the order ID. You cannot refund what has not been captured. A refund creates a new credit flow and is itself asynchronous. The refunded state is terminal, though a disputed transition is possible if the cardholder still files a chargeback.

### Disputed
The issuer opened a chargeback on the cardholder&#x27;s behalf. According to the Mastercard US Disputing Chargebacks Guide, the merchant&#x27;s response deadline is typically 20 to 45 days after notification. The entire process can extend up to 120 days. Disputed is effectively terminal for the original payment. Your system needs a dispute sub-state (open, evidence submitted, won, lost) rather than a single disputed flag.

Image: Each state limits which transitions are valid and who may trigger them. Illustrative — based on Mastercard Integration Guide and Yuno API Documentation. Created: Can be captured: Not yet; Can be voided: Not yet; Can be refunded: Not yet; Can be disputed: Not yet; Who triggers next step: Customer or timeout. Pending: Can be captured: Awaiting async; Can be voided: Not allowed; Can be refunded: Not allowed; Can be disputed: Not allowed; Who triggers next step: Provider webhook. Authorized: Can be captured: Full or partial; Can be voided: Before bank recv; Can be refunded: Capture first; Can be disputed: Not yet captured; Who triggers next step: Merchant, 5-8 day window. Captured: Can be captured: Already done; Can be voided: Too late; Can be refunded: Refund allowed; Can be disputed: Cardholder claim; Who triggers next step: Merchant or cardholder. Voided: Can be captured: Terminal; Can be voided: Terminal; Can be refunded: Terminal; Can be disputed: Terminal; Who triggers next step: None — end state. Refunded: Can be captured: Terminal; Can be voided: Terminal; Can be refunded: Terminal; Can be disputed: Possible; Who triggers next step: Issuer if disputed. Disputed: Can be captured: Terminal; Can be voided: Terminal; Can be refunded: Terminal; Can be disputed: Terminal; Who triggers next step: Issuer — up to 120 days.
Each state limits which transitions are valid and who may trigger them. Source: Illustrative

## Who is allowed to trigger each transition
Every transition has exactly one allowed actor class, and mixing them up is the most common cause of premature state advancement. The four actors are: the customer, the issuer, the merchant, and the payment provider.
The customer creates the intent and can abandon before authorization. The issuer approves or declines authorization and later opens disputes. The merchant triggers capture, partial capture, void, and refund. The payment provider delivers the webhook that confirms async events. Your application code is never an actor; it only listens and routes.
The practical consequence: never advance a state in response to a timeout or a UI event. Advance it only in response to a confirmed webhook from the provider. A user clicking "pay" does not authorize a transaction. The issuer&#x27;s response authorizes it.

## Asynchronous methods and why they break naive implementations
Synchronous card flows return a result in the same API response; asynchronous methods return a pending state and deliver the result later via webhook. Treating async methods as synchronous is the single most common payment state bug.
The failure pattern is predictable. An engineer integrates cards, which resolve in two seconds. The checkout flow reads the response body, finds "approved," and fulfills the order. Six months later, the team adds PIX or ACH. Those methods return pending. The fulfillment logic reads pending as failure, or worse, reads nothing at all. Orders are never fulfilled. Refunds are issued for transactions that settle the next day.
The fix is architectural. Every integration, synchronous or not, must write the initial status and then wait for the webhook to advance it. The response body is for display. The webhook is the source of truth.

## Designing webhook handling that survives the real world
Production webhook handling has three requirements: idempotency, ordering tolerance, and guaranteed acknowledgment. Miss any one and a single retry creates a duplicate fulfillment, a missed state transition, or an infinite delivery loop.

### Idempotency
According to the Yuno API Documentation on webhooks, your endpoint can receive the same event more than once. This is expected behavior. The implementation is simple: store the event ID. On every inbound webhook, check the ID against your log before processing. If it exists, return HTTP 200 and exit. If it is new, write it, apply the transition, then return HTTP 200.
The same principle applies to payment creation. According to the Yuno API Documentation on authentication, if a first request with a given idempotency key created a payment, any later request with the same key returns that original payment instead of creating a new one. Use idempotency keys on every write, not just on webhooks.

### Out-of-order tolerance
Webhooks do not always arrive in sequence. A captured event can arrive before the authorized event if a network partition delayed the first delivery. Your state machine must reject illegal transitions rather than apply them blindly. If your system holds no authorized record, a captured event should queue, not overwrite. A simple check: the new state must be reachable from the current state. If it is not, hold the event and retry processing after a short delay.

### Guaranteed acknowledgment
Return HTTP 200 only after you have durably written the event ID and applied the transition. If you return 200 before writing to the database and the process crashes, the event is marked delivered but was never processed. Return a 5xx if your database is unavailable. The provider will retry, which is exactly what you want.

## Worked example: adding a second async payment method
This company is hypothetical. All figures below are illustrative assumptions, with every assumption stated.
Assume a merchant processes 10,000 transactions per day through card rails. Authorization hold windows run five to eight days, per the Mastercard Integration Guide. The team decides to add a local bank-transfer method that settles asynchronously, typically within one business day.
If the webhook handler is not idempotent, every provider retry during a network hiccup creates a second state transition. Assume the provider retries each failed delivery three times and 2% of webhook deliveries encounter a transient failure. That means 200 transactions per day receive a retry (10,000 × 0.02 = 200). Without idempotency, 200 orders per day risk double-fulfillment or double-refund. With an event-ID log, all 200 are silently de-duplicated. The cost of adding the second method: one additional column in the event log table and one additional entry in the provider configuration. No new state-machine code is needed, because the states are identical across both methods.
The assumptions behind the retry exposure:

- Daily transactions: 10,000 (assumed)
- Transient failure rate: 2%, giving 200 affected deliveries per day
- Provider retry count per failed delivery: 3 (assumed)
- Without idempotency: up to 200 duplicate state transitions per day
- With idempotency: zero duplicate state transitions, regardless of retry count

## What this means for your tenth payment method
The question engineers often phrase as "which payment platform has the simplest API integration" points at the wrong variable. The right question is which design keeps the tenth integration as cheap as the first.
The answer is a shared payment state machine and an idempotent webhook handler. New payment methods slot in as new provider configurations, not new state logic. Merchants asking which payment platform requires the least engineering maintenance over time will find that answer in the architecture, not the vendor list.
Yuno, the AI-native intelligence system of global payments and financial services, is built on this principle. One integration reaches 1,000-plus payment methods across 190-plus countries. Yuno has processed over one billion payments across 150-plus currencies since January 2025. Because every provider&#x27;s events are normalized to the same state vocabulary before they reach a merchant&#x27;s webhook endpoint, adding a new method requires no changes to the state machine. Yuno reports a 90% reduction in development time for merchants adding new providers this way.
Merchants such as Rappi and inDrive operate across dozens of markets and payment methods. In markets where local bank transfers and wallets dominate alongside cards, the state machine discipline described here is what keeps engineering costs flat as the method count grows.

## The rule to put on the wall
Every payment incident starts with a question: did the code advance the state, or did the provider? If code advanced it, that is the bug. The state machine&#x27;s job is to make that question answerable in under a minute. Build the model first. Connect providers second. The order is not a preference; it is the reason adding the tenth method stays cheap.
Start today by listing every state your current system can assign to an order. For each state, write down which actor is allowed to trigger it. Find any state that your application code advances on a timer or a UI event. Those are your incident backlog.
