> ## Documentation Index
> Fetch the complete documentation index at: https://conto.finance/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Track and recover a payment

> Understand decisions, human review, settlement, and safe recovery after an uncertain result.

A payment request is an intent to spend. Authorization permits the next step; it does not prove
funds moved. A transaction records execution. Retain the request ID before executing, then use it
to reconcile the outcome.

## The lifecycle

```mermaid theme={null}
flowchart TD
  Request[Request authorization] --> Decision{Decision}
  Decision -->|Denied| Stop[Stop and show reason]
  Decision -->|Review required| Review[Human review]
  Decision -->|Action required| Action[Complete returned action]
  Review --> Read[Read original request status]
  Action --> Read
  Decision -->|Approved| Execute[Execute or externally send]
  Read -->|Approved| Execute
  Execute --> Processing[Processing]
  Processing --> Complete[Completed]
  Processing --> Failed[Failed]
```

Use `GET /api/sdk/payments/{requestId}` or `conto.payments.status(requestId)` with the same agent
key. Status reads require `transactions:read`. The status endpoint uses lowercase values;
authorization responses use uppercase values. Do not compare the two interchangeably.

| Status read               | Meaning                                     | What your application does                                                |
| ------------------------- | ------------------------------------------- | ------------------------------------------------------------------------- |
| `approved`                | Authorization permits the next payment step | Check expiry and your stored execution state before sending               |
| `review_required`         | Waiting for a human decision                | Retain the ID and wait for an authorized reviewer                         |
| `action_required`         | Another customer action is needed           | Present `action.url`, then check this request again                       |
| `reconciliation_required` | Execution evidence needs reconciliation     | Retain the request and transaction IDs; investigate before another write  |
| `processing`              | Execution or reconciliation is in progress  | Poll with a deadline or consume webhooks; do not send again               |
| `completed`               | The payment flow completed                  | Inspect the transaction and the original execution receipt                |
| `declined`                | Controls or review stopped the request      | Explain the result; do not execute                                        |
| `failed`                  | The payment flow failed                     | Reconcile transaction evidence before deciding on a new intent            |
| `expired`                 | Authorization expired                       | Do not execute this authorization; decide whether a new request is needed |

## What proves settlement?

Execution returns `receipt.transactionId` and `receipt.settlementMode`.
`live` means an onchain transfer, including testnet; `test` means simulated settlement. New test
receipts omit transaction hashes and explorer URLs. Preserve the receipt and request ID. Status
and transaction endpoints repeat `settlementMode`; `unknown` means execution evidence is absent
or the legacy record cannot establish the mode. Never infer onchain settlement from `completed`
or a legacy hash alone. A simulated record has no blockchain confirmation time or block number.

For an onchain flow, wait for `transaction.status: "completed"` and check the intended chain.
A hash alone, an approval, or HTTP 200 is not confirmation. For a sandbox flow, a completed test
receipt demonstrates the API workflow without a blockchain transfer.

## Bounded polling

Call this helper after submission, or after an uncertain result. It only reads state. A
non-processing state returns control to your application, including review, expiry, and denial.
Configure the SDK request timeout too: an in-flight HTTP call can finish after the polling deadline.

```typescript theme={null}
import { Conto, type PaymentStatusResult } from '@conto_finance/sdk';

export async function waitForPayment(
  conto: Pick<Conto, 'payments'>,
  requestId: string,
  timeoutMs = 60_000
): Promise<PaymentStatusResult> {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const state = await conto.payments.status(requestId);
    if (state.status !== 'processing') return state;
    await new Promise((resolve) => setTimeout(resolve, 2_000));
  }
  throw new Error(`Still processing: retain ${requestId} and check again later`);
}
```

If a read fails with 429, honor `Retry-After`; resume later with the same request ID. A polling
deadline or read failure does not cancel the payment. See [rate limits](/docs/guides/rate-limits).

## Recovery recipes

### Execution timed out

1. Read the original request ID. A timeout is not proof that execution failed.
2. If there is a transaction, follow that transaction to completion or failure.
3. If the state is unresolved, retain it and investigate; do not automatically repeat execution
   even if the authorization still reads `approved`.
4. Create a new intent only after establishing the original outcome and confirming the business
   action still needs payment. Preserve the association between both intents.

A repeated execute can return `400 INVALID_STATUS` after completion or `409 ALREADY_EXECUTED`
for an execution conflict. Reconcile the original request in either case.

Persist your business action ID, idempotency key, Conto request ID, execution attempt, and receipt
durably. A restarted worker must be able to discover an earlier execution attempt.

### A human must approve

Keep `REQUIRES_APPROVAL` requests pending in your application. An authorized reviewer decides in
the configured [approval workflow](/docs/guides/approval-workflows). Check the same request ID after
review: an approved decision is not a settlement receipt, and the workflow may already have begun
execution. Only perform a next write when its state and your stored attempt history permit it.

### The SDK throws ACTION\_REQUIRED

HTTP 402 rejects the SDK promise. Catch `ContoError`, preserve `error.requestId`, and present
`error.actionUrl`. After the customer finishes, read the original request instead of creating
another payment. See the [request example](/docs/sdk/payments#example).

### External confirmation returns a conflict

Send through your external signer only after approval. Confirm using the request ID, final hash,
and the approval token when one was issued. After human workflow approval, the token can be omitted.

Repeated confirmation does not duplicate recorded spend but can return `409 ALREADY_CONFIRMED`.
Compare the returned `txHash` with your saved transfer and follow `statusUrl`. If the hashes differ,
stop and investigate. Never send a second transfer to resolve a confirmation error.

### The wallet has insufficient funds

Inspect the intended wallet's address, network, currency, and available balance. Funding another
wallet or chain does not repair this request. After funding, re-read the request and check expiry
and prior execution evidence before deciding whether to resume or request new authorization.

### A webhook is missing or repeated

Use the payment status endpoint to reconcile. Verify webhook signatures and persist accepted
events before returning success. Handle duplicate or out-of-order delivery without creating a new
payment; the current resource state should determine your application's next action. Follow
[webhook delivery and deduplication](/docs/guides/webhooks).

## Next steps

Use [server integration examples](/docs/guides/framework-examples) to connect recovery to an authenticated
application route, and exercise these paths with [payment tests](/docs/guides/testing-payments).
