# Track and recover

Documentation revision **2026-09-27**. Every URL below is relative to `/v1`. Keep the business order, network, quote ID, payment ID, preparation version, authorization ID, request key, exact request body and response request ID together. Keep signatures and secrets out of application logs.

Read [`GET /payments/{id}/next-action`](next-actions.md) for the normalized next step and responsible actor. It observes saved state; it does not perform reconciliation or refresh provider/chain evidence itself. `review-required` pauses automatic execution. Preserve any in-flight operation and its original request identity before acting on a newer snapshot.

**A transport error does not tell you whether a mutation committed.** Read the existing payment and transaction before deciding to retry. `sourceHash` can be calculated when signing; its presence is not proof of broadcast. Once a transaction is submitted or uncertain, quote/review expiry is no reason to fund again.

## Payment states

`paymentState` is the normalized observed state alongside the existing draft `status`. This vocabulary is shared by single-transfer, composed and testnet payments. Draft `status`, provider `tracking.status` and composed `workflow.state` remain available as detailed, backward-compatible fields; use `paymentState` for your payment-level display. A provider success message alone cannot mark settlement complete.

| Payment state | Meaning |
| --- | --- |
| `DRAFT` / `ARCHIVED` / `CANCELLED` | Unprepared, archived, or safely closed payment. |
| `PREPARING` / `AWAITING_ACTION` | Preparation is running, or the next wallet action is available. |
| `WALLET_SUBMISSION_UNKNOWN` | Route handed an action to the wallet but has not verified its transaction hash. It may already be broadcast; recover and report the existing hash, never send again automatically. |
| `INTERMEDIATE_HELD` | Earlier transfers are final and the composed payment is ready for its next transfer; funds are in the intermediate wallet. |
| `REQUIRES_ATTENTION` / `FAILED` | Evidence or readiness needs review, or a source transfer provably reverted. Any other tracking error, such as a receipt that does not prove the saved action or a removed provider connection, is kept in `preparation.lastError` and reads as this state until a later observation succeeds; a temporary outage in between does not replace it. Follow `next-action`. |
| `SOURCE_PENDING` | A source submission is recorded; source confirmation or finality is pending, with no verified delivery or refund yet. |
| `DESTINATION_PENDING` | The source is verified and final; destination verification is still pending. |
| `VERIFICATION_PENDING` | Receipt verification is incomplete, including temporarily unavailable chain data. A temporary receipt, provider-status or destination-lookup outage (`preparation.lastError.code` `TRACKING_UNAVAILABLE`, `PROVIDER_TRACKING_UNAVAILABLE` or `DESTINATION_DISCOVERY_UNAVAILABLE`) shows this state instead of a pending source or destination while tracking retries, and emits no webhook. An uncertain send, a wallet handoff or a required wallet action keeps its own state. Read `preparation.tracking.message` or `preparation.lastError.message` for the reason. |
| `DELIVERED_PENDING_FINALITY` | Exact source and recipient receipts are verified, but at least one chain has not reached finality. Display “Delivered — awaiting finality.” |
| `REFUND_PENDING_FINALITY` | Exact source and refund receipts are verified, but finality is still pending. |
| `COMPLETED` / `REFUNDED` | Both required receipts are verified and final. |

For example, Stellar can have a final recipient receipt while Ethereum's source receipt still awaits finality. This is delivery awaiting finality, not a missing source transfer. Continue tracking the same payment without requesting another signature or sending again. `preparation.tracking.status` remains `verification-pending` until finality; the individual `sourceVerified`, `sourceFinalized`, `destinationVerified`, `destinationFinalized`, `refundVerified` and `refundFinalized` flags give the precise evidence state. NEAR EVM receipt reads use reviewed fallback endpoints after temporary transport, rate-limit, access or lagging-finality observations. Route re-verifies the complete receipt, or corroborates an already verified receipt’s exact canonical block hash on the same chain against a current finalized checkpoint. Invalid or mismatched chain evidence never permits completion.


## Bitcoin confirmation and delivery

Track these observations separately:

1. **Submitted:** Route has recorded the Bitcoin transaction ID. This is not proof of inclusion.
2. **Source verified:** the exact deposit is included in a canonical Bitcoin block. NEAR may still report `PENDING_DEPOSIT` until it recognizes the funding; the initial confirmation is not an end-to-end delivery estimate.
3. **Delivery verified:** Route has verified the destination transaction, exact asset, recipient and amount. `paymentState: "DELIVERED_PENDING_FINALITY"` means that delivery is proven but source or destination finality is still pending.
4. **Completed:** both receipts are verified and final. Route requires **six Bitcoin confirmations** for Bitcoin source or destination finality. Its wallet builder can spend a confirmed canonical UTXO before six confirmations; that does not change the tracking rule. The composed-payment API separately requires finalized intermediate settlement.

Read the same payment and refresh its existing preparation. A quote or signing-review expiry after submission does not undo the transaction or authorize another deposit. Provider estimates start after deposit confirmation and do not guarantee Bitcoin block timing. No additional source transfer is needed simply because provider recognition or finality is pending.

For Bitcoin destinations, ordinary delivery verification compares the canonical recipient net credit with NEAR's reported amount exactly. Ethereum ERC-20 → native Bitcoin fee-reserve returns can also settle when Route verifies the complete intent/withdrawal/output proof. Every new BTC delivery completion requires a persisted exclusive payout claim. Read `tracking.bitcoinPayout` and `tracking.received` for the actual receipt; the original provider amount remains unchanged. Other unresolved amount mismatches stay `VERIFICATION_PENDING`. Preserve the same payment and use normal refresh; neither provider success nor a wallet balance increase establishes settlement. See [Bitcoin payout reconciliation](recovery.md#bitcoin-fee-adjustment-discrepancies) and the [funded example](release-status.md#native-bitcoin-to-stellar-funded-evidence-and-tracking-exception).


## Read saved recovery evidence

Use `GET /payments/{id}/recovery` with `payments:read`. It is available on both networks; select the correct network key and `Route-Network` header. The report belongs to the authenticated project and policy. A composed report names the root `paymentId` and `activePaymentId`, and can reference the next step on the active child.

For a composed payment, completion of one child does not establish completion of the whole route. Follow the root payment's next action; it may require refreshing the workflow or advancing to another leg. A verified refund of a later leg returns funds on that leg's source network, not necessarily the original source network or asset. The root recovery report identifies this as `intermediate-held` with operator review, while the workflow shows `needs-review`. Inspect `evidence.refund` and the child payment's asset and refund instructions before arranging any further transfer. Do not resend the original source payment. A first-leg refund is reported as `refunded` once its source and refund receipts are verified and final.

```js
import {createRouteSDK} from './route-sdk.mjs';

const route = createRouteSDK({
  baseUrl: 'https://route.click/v1',
  apiKey: process.env.ROUTE_API_KEY,
  network: 'mainnet'
});
const report = await route.getRecovery(savedPaymentId);
// Present report.summary, report.owner and report.requiredEvidence for review.
// This GET does not execute report.nextStep.operation.
```

`savedPaymentId` is your existing payment ID. The SDK validates response identity, network, schema version, nested types and the report's closed set of safe fields. An unsupported or inconsistent report throws `INVALID_RECOVERY_REPORT`; keep the original payment and request review. See the complete `PaymentRecoveryReport` schema in [mainnet OpenAPI](openapi.json) and [testnet OpenAPI](testnet-openapi.json), and the [generated response examples](integration-fixtures.json).

| Report field | Meaning |
| --- | --- |
| `outcome`, `owner`, `summary` | Saved recovery classification, responsible party and explanation |
| `paymentId`, `activePaymentId`, `preparationId`, `authorizationId` | References to preserve; preparation or authorization may be null |
| `generatedAt`, `evidence.lastObservedAt` | Report creation time versus the last saved tracking observation; neither guarantees fresh chain data |
| `nextStep` | An action reference without a wallet payload. Read current next-action and detailed transaction checks before authorizing it |
| `evidence.source`, `destination`, `refund` | Saved hash, verification, finality and inclusion time for each stage |
| `evidence.transactions` | Saved action identity, state, submission-attempt flag and receipt observation. Testnet includes its latest action, not full history |
| `evidence.settlementDiscrepancies` | Exact provider and recipient amounts, associated transaction and canonical block evidence |
| `requiredEvidence` | What still needs to be verified before the issue can be resolved |
| `reservations` | Existing wallet sequence/Bitcoin input, API spending-limit and Route-credit accounting observations |
| `safety` | `automaticReplacementAllowed` and `reservationReleaseAllowed` are always false |

An unobserved transaction receipt has `receipt.observed: false` and nullable flags. Null means the saved view lacks that evidence; it does not prove that a transaction was absent or failed. Separate source/destination tracking can already contain verified evidence. Similarly, `submissionAttempted: null` is unknown rather than proof that nothing was broadcast.

Mainnet reads existing reservation records and returns up to 150 items per group, with `truncated: true` when more were omitted. Testnet returns `observed: false`, `items: []` and `truncated: false` for all three groups. Do not interpret an unobserved empty list as proof that a wallet nonce or input is available. Reading the report cannot release a reservation or adjust billing. API-limit commitment records source funding; it is separate from verified destination completion and Route usage credits.

| Outcome | Interpretation |
| --- | --- |
| `no-action-issued` | No wallet action or provider preparation is recorded for this draft |
| `awaiting-wallet-action` | Continue only with the saved next step and its current checks |
| `unsigned-review-expired` | Renew only through a same-payment operation explicitly offered for that action |
| `signed-not-submitted` | A signature is saved without recorded submission or verified receipt evidence; the hash alone is insufficient |
| `submission-uncertain` | Track the exact saved action; submission may have reached the network |
| `source-confirmed` | Source funding is verified but awaits finality |
| `destination-pending` | Source funding is final; final destination delivery is not yet verified |
| `settlement-discrepancy` | Provider and chain evidence conflict; exact attribution needs operator review |
| `intermediate-held` | A composed workflow's first leg is final; the next leg still needs preparation and authorization |
| `completed`, `refunded`, `closed` | Respectively finalized delivery, finalized refund, or a closed unused payment |
| `operator-review` | The saved state cannot be continued safely; preserve its evidence |

**Saved report and refresh are different operations.** This GET makes no provider or chain request and does not mint, submit, allocate a deposit, replace a payment or release reservations. `POST /payments/{id}/refresh` requests fresh evidence and may also perform previously authorized managed destination minting. Do not invoke refresh merely to generate a support report.

## Decide the recovery action

| Condition | Could payment funds have moved? | Next endpoint / action | Request identity | Retry or escalation rule |
| --- | --- | --- | --- | --- |
| Quote creation times out | Quote itself moves no funds | Retry `POST /quotes` | Same key and identical body | Back off; a fresh comparison uses a new key, explicitly linked to the same business order |
| Draft creation times out | Draft itself moves no funds | Retry `POST /payments` | Same key and identical body | Recover the original draft ID before considering a replacement |
| Preparation times out or returns `202 needs-review` | It can allocate instructions or reserve a wallet slot; existing funding must be checked | `GET /payments/{id}/preparation`; NEAR `POST /reconcile` with `{}` only when `canReconcile: true` | Mainnet preparation retry: same key/body. Testnet `/prepare`: saved version/body, no request key contract | If existing activity or an ambiguous allocation remains, retain this payment and escalate |
| `MINIMUM_OUTPUT_NOT_MET` | Do not assume the allocated deposit is unfunded | Read preparation, then supported `/reconcile` | Same payment; reconciliation has no new creation key | Requote only when returned `recovery.canRequote: true`; do not infer that permission from a lower output alone |
| Transaction creation response lost | Creation itself does not submit; another worker might have progressed | `GET /payments/{id}/transaction`, then matching `POST /transaction` if needed | Same key and original body/version | Recover the saved authorization; do not choose a new nonce |
| Signature acceptance response lost | Signed bytes exist; external submission may be possible | Read `/transaction`; retry `/transaction/signature` only for the same authorization | Same authorization and exact bytes; no idempotency header required | If already signed, proceed according to current flags. If submitted/uncertain, track |
| `/execute` times out | **Yes** | Read `/transaction`, then `POST /refresh` | Same payment and authorization | `submitting`, `submitted`, `submission-unknown`: track only. Retry execute only if it is still `signed`, current, submission-enabled, and authorized |
| `TRANSACTION_NOT_READY` / `TRANSACTION_PRICE_EXPIRED` | The rejected preflight does not start a new send; check saved submission state | Read `/transaction` and preparation; display `error.details.blockers` or saved diagnostics | Preserve authorization, signed bytes and nonce | Resolve the specific failed/unknown check; retry same execute only while signed, current and enabled. Persistent unavailable evidence needs support |
| Mainnet EVM approval review expired | Allowance may exist if someone broadcast the bytes | Read transaction; if `reviewRefreshEnabled`, `POST /transaction/review` with authorization ID | New saved key for a new review; same key/body on retry | Recheck the existing approval, then sign or submit that action as allowed. Submitted actions are tracked |
| Stellar CCTP approval expired without an included receipt (unsigned, signed or submitted) | **No.** An approval moves no funds itself. A wallet may still have broadcast it without returning its signature, so renewal checks the network first | When `next-action` is `renew-approval`, `POST /transaction/renew` with the authorization ID. When it is `close-payment` (renewal cannot run), `POST /transaction/close` | Save a new renewal or close key; same key and body on retry | Route proves ledger expiry, no receipt and an unused sequence, then prepares a fresh action at the same sequence for a new signature. A found transaction is [adopted and tracked](#a-wallet-broadcast-the-saved-stellar-action-itself); a used sequence is recorded for review. Any other `APPROVAL_RECONCILIATION_REQUIRED` changes nothing: refresh and retry later, or escalate |
| `APPROVAL_STATE_CHANGED` on a saved Stellar approval or burn | No new send starts. Only a submitted action could have moved anything | Refresh, then read `next-action` | Keep all IDs; do not sign or submit the saved action | The allowance changed: an approval is no longer needed, or a burn's allowance would lapse before the burn expires. Let the saved action expire. An expired approval or unsigned burn can then be renewed; a signed burn offers `close-payment` |
| Stellar burn or transfer expired without an included receipt | **Possibly.** Route may have no signature, but a wallet can broadcast the returned envelope itself before its signed time bound. After that bound it cannot execute | Unsigned: when `next-action` is `renew-source`, `POST /transaction/renew`. Signed, submitted or uncertain, or unsigned when renewal cannot run: when it is `close-payment`, `POST /transaction/close` | Save a new renewal or close key; same key and body on retry | Renewal prepares a fresh action at the same sequence with fresh terms. Closing ends the unfunded payment and releases its wallet sequence and holds. Both first check the saved hash: a found transaction is [adopted and tracked](#a-wallet-broadcast-the-saved-stellar-action-itself) and a used sequence is recorded for review. Any other `SOURCE_RECONCILIATION_REQUIRED` or `CLOSE_UNAVAILABLE` changes nothing |
| `WALLET_SLOT_RESERVED` from a Stellar wallet | No. Another Route action holds the same wallet sequence | Find the other payment's current action: renew it, finish it, or close it when `closeEnabled` is true | Keep this payment unchanged | Only renewal, inclusion, closure or an unrelated wallet transaction that advances the sequence frees it. Route never releases another project's reservation |
| Allowance expiring under a Stellar burn | Not before submission. A submitted burn can fail on-chain and still charge its fee | Refresh before signing; Route rechecks allowance lifetime at signature and submission | Same payment and authorization | Route builds a burn only when the allowance stays live through the burn expiry plus 12 ledgers. Otherwise it returns an approval, or rejects a saved burn with `APPROVAL_STATE_CHANGED` |
| Stellar approval sequence consumed (`submissionObservation.state: "sequence-consumed"` or `"sequence-gap"`) | **Possibly**: this or another transaction used or skipped the wallet sequence | Refresh the same payment; recover the original receipt | Keep all IDs; the wallet sequence stays reserved | Renewal and closing are refused. A renewal or close check that finds a used sequence records it, also for a never-signed action, with `lastError.code: "STELLAR_SEQUENCE_REVIEW"`, and moves the preparation to `state: "needs-review"`: `paymentState` stays `REQUIRES_ATTENTION` and `payment.requires_attention` is emitted once. Operator review establishes what used the sequence before any new action |
| Native NEAR-chain allocation review expired; no transaction exists | No Route transaction was constructed | When `reviewRefreshEnabled`, `POST /transaction/review` with current preparation version | Save a new review key and reuse it on retry | Confirm the new NEP-413 intent approval, then build the first transaction. Existing actions require reconciliation |
| Mainnet NEAR EVM deposit review expired | Signed bytes remain valid on-chain | Read preparation/transaction; when enabled, `POST /transaction/review` with current preparation version and saved authorization ID | New saved review key; preserve payment and transaction | Confirm the **new payment intent approval**, then use the existing transaction. Original provider/refund deadlines still apply |
| Testnet unsigned action review expired | Route has no accepted signature, but reconcile any external wallet attempt | Read payment/transaction; `POST /prepare` with current payment version | Same payment; no idempotency key on testnet prepare | Server permits only a valid same-nonce unsigned refresh; sign only the new authorization |
| Signed testnet action or unsupported protocol transfer review expired | **Yes: signed EVM bytes do not expire on-chain** | Read transaction and refresh its evidence | Keep all original IDs; no replacement key | Stop automatic execution; operator reconciliation is required. Do not reuse the approval-renewal endpoint for a burn |
| `VERSION_CONFLICT` / `IDEMPOTENCY_CONFLICT` | Existing work may have advanced | Read payment/preparation and saved transaction | Preserve the key for its original body; never reuse it with changed data | Adopt verified existing progress. A genuinely new authorized operation gets a new key and current version |
| `DEPOSIT_ALREADY_ACTIVE` / `DEPOSIT_NONCE_CHANGED` | **Possibly** | Refresh deposit/transaction and reconcile the wallet | Same payment; no automatic new deposit | Active funding is tracked. A changed wallet nonce needs reconciliation |
| Destination discovery unavailable / `VERIFICATION_PENDING` | **Yes; recipient may already be paid** | `POST /refresh`; optionally supply the exact known `destinationHash` | Same payment; no new source action | Route still verifies binding and credit. Persistent mismatch, missing proof or unavailable history needs support |
| SwapKit key removed, or SwapKit tracking cannot find the swap (`PROVIDER_CREDENTIAL_REQUIRED`, `PROVIDER_TRACKING_UNAVAILABLE`) | **Yes; delivery or a refund may already have happened** | Delivery: reconnect the original SwapKit connection, then `POST /refresh`, optionally with `{"destinationHash":"HASH"}`. Refund: reconnect the original connection, then `POST /refresh` with `{"refundHash":"HASH"}`, or a workspace member confirms it in the dashboard | Same payment; no new source action | A destination hash must match the delivery leg SwapKit reports for the saved source transfer, then pass chain verification of the recipient, asset, reported amount, minimum, time and finality before an exclusive payout claim. Without provider attribution the payment stays unsettled and its reservations remain. A refund hash settles only when SwapKit reports that exact transaction as the refund leg, because any wallet can credit the refund address. A dashboard confirmation is still verified on chain and bounded by the deposit; see [confirming a refund in the dashboard](providers.md#confirm-a-swapkit-refund-in-the-dashboard) |
| `BITCOIN_SETTLEMENT_AMOUNT_MISMATCH` in `tracking.settlementDiscrepancies[]` | **Yes; a canonical recipient credit exists** | Read the saved discrepancy; use the same-payment refresh or scheduled tracking for supported allocation verification; escalate if proof is missing or conflicting | Same payment, source hash and associated payout hash | Compare reported/net observed satoshis, minimum, canonical block and confirmations. A larger output does not establish payment-specific attribution; do not fund again or mark complete locally |
| Managed mint `failed` / `submission-unknown` (`REQUIRES_ATTENTION`, `review-required`) | Source funds already moved; destination outcome may be uncertain | Read the preparation's `destinationExecution.lastError` evidence (relay ID and hashes, or the lost-response reason and attempt time) and any earlier `relayAttempts`. If the Stellar mint exists, supply its hash to `/refresh` as `destinationHash` | Same source message and payment | With `lastError.retryAllowed: true`, Route re-mints the same saved call once after `lastError.remintAfter`, only while the CCTP nonce is unused and the attestation and call still simulate; a duplicate can only fail on chain. After that, or when `retryAllowed` is `false` (including `DESTINATION_MINTED_UNRECORDED`), Route verifies a supplied receipt but does not re-mint; without a mint an operator decides the recovery. Never repeat the source burn |
| `429`, transient `5xx`, unavailable network read | Depends on which operation failed | Read/replay only the original operation where safe | Same mutation key/body or authorization | Honor `Retry-After`; bounded backoff. Stop after repeated failures; no blind chain rebroadcast |
| `401` / `403` | Prior transactions can still exist | Correct key scope, network or owner access; reread saved payment | No replacement payment | Do not weaken owner policy to bypass a rejection |

Mainnet review capabilities are limited to the [approval](source-wallet-readiness.md#refresh-an-expired-approval-review) and [NEAR deposit](payment-transactions.md#refresh-an-expired-near-deposit-review) cases described above. Stellar CCTP approvals cannot refresh a review; they use [approval renewal](#renew-an-expired-stellar-cctp-approval). A suggested next action never overrides `signingEnabled`, `submissionEnabled`, actual blockers or expiry. Unknown future states require a pause, continued safe reads and an updated integration.

For testnet, `authorizationPrepared: false` means the prepared unsigned action has not yet received a persisted signing authorization. Save and submit a `POST /transaction` request before requesting a signature; then require `authorizationPrepared` and `signingEnabled`. This is not a reason to create another payment or change its nonce.

## Worked recoveries

### A draft response was lost

Save request key `K1` and `{"mode":"prepare","quoteId":"Q1","candidateId":"C1"}` before sending. If the connection closes, resend exactly that body with `K1`. The response returns the same payment `P1`. Store `P1` before starting preparation. Generating `K2` after the timeout could create a second draft for the same order.

### An approval expires before submission

Read its authorization `A1`. If it is submitted or uncertain, refresh; expiry does not undo the transaction. If this is a supported mainnet EVM approval with renewal enabled, save review key `R1`, renew `A1`, and inspect the returned checks and expiry. A saved signature is reused only for the unchanged payload. After a successful canonical approval block, prepare the transfer with a new action key and current preparation version. Review its fresh terms separately.

A Stellar CCTP approval has no review refresh. When `next-action` returns `renew-approval`, save renewal key `R1` and call `POST /transaction/renew` with `{"authorizationId":"A1"}`. Route replaces `A1` only after proving it can no longer execute. The response is replacement `A2`, at the same wallet sequence, which needs a new signature; `A1` and any signature for it are retained. If a live allowance already covers the burn through its expiry, `A2` is the burn itself. Otherwise sign and submit `A2`, wait for a finalized ledger, then prepare the burn with a new action key.

### A signed deposit fails current readiness checks

Keep `A1` and the exact signed transaction. Display each returned blocker, such as an unavailable price feed or insufficient gas balance. Read the transaction after the error. If it is still signed and current, retry the same authorization only after the blocker is resolved and submission is authorized. If its NEAR EVM review expired, renew the review and confirm the new intent challenge. If renewal is unavailable, pause; do not request a replacement signature as a workaround.

### Execution returned no response

Read `A1`. If `walletBroadcast.state` is `awaiting-transaction`, follow [wallet handoff recovery](#wallet-handoff-with-a-missing-response). For the sign-only/Route submission flow, `submitting` or `submission-unknown` means Route has recorded its broadcast attempt. Refresh `P1`; the stored hash is the tracking reference. Do not call a wallet's send method, start another provider deposit or change the gas price. If the chain cannot establish the outcome, escalate with the original evidence. At-most-once Route submission prevents duplicate Route broadcasts; it cannot erase signed bytes or prevent an external party from broadcasting them.

## Polling and retry schedule

Use `GET /payments/{id}` for saved state; `POST /refresh` requests new provider/chain evidence and may advance managed delivery. After submission, poll refresh at **15 seconds**, then **30**, then **60**, adding 0–20% jitter. Honor a longer `Retry-After`. Keep one active tracker per payment, stop on completion, and switch unresolved errors to an operator queue after five consecutive transient failures. A worker restart uses the same durable state.

These are recommended client intervals, not rate limits or an arrival promise. Some limits apply across a project: staggering a large number of payments matters. Read response rate-limit headers and the [API limits](customer-api.md#retries-limits-and-errors); a maximum allowed request rate is not a recommended polling frequency. Webhook delivery must be enabled for your approved HTTPS origin; until then, use controlled polling. Even with webhooks, deduplicate events and reread payment state before acting.

## Support bundle

In the dashboard, open a payment's **Recovery details**, then **Download support details**. The JSON contains the allowlisted report described above. It omits credentials, signatures, signing challenges, transaction payloads and raw provider responses. Wallet addresses, transaction hashes and public record IDs remain necessary evidence; share the bundle only with authorized support staff. Downloading it does not refresh or modify the payment.

Provide network, business-order reference, payment/preparation/authorization IDs, request ID, endpoint, timestamp, error code, redacted blocker messages, source/destination hashes and the last known state. Include both provider-reported and on-chain amounts for a receipt mismatch. **Never include Route/provider credentials, private keys, raw signatures or signed transaction bytes.** Preserve the private operation record until reconciliation is complete.

## Bitcoin fee-adjustment discrepancies

A BTC payout can exceed the provider-reported amount when a bridge returns unused network-fee reserve. In the investigated payout, withdrawal-bound bridge events show a 1,500-satoshi network-fee reserve but 376 satoshis actually spent. The 1,124-satoshi difference explains a reported 9,784 versus received 10,908. See the [bound transfer evidence](https://mainnet.api.bridge.nearone.org/api/v4/transfers/transfer?origin_chain=Near&origin_nonce=525197) and [release verification](release-status.md#native-bitcoin-to-stellar-funded-evidence-and-tracking-exception).

For supported Ethereum ERC-20 → native Bitcoin deliveries through NEAR 1Click, Route now verifies the exact source deposit, NEAR intent and withdrawal, bridge nonce, connector submission, successful withdrawal verification and canonical recipient output. Both chains must satisfy finality, and the received amount must satisfy the saved minimum. The provider/indexer status alone is insufficient. Route persists an exclusive payout claim in the same database transaction as completion; retries cannot count that payout twice. Other unresolved delivery or refund amount mismatches remain pending for review; ordinary exactly verified refunds keep their existing completion rules.

Read `preparation.tracking.bitcoinPayout` for the proof method and `claimId` after persistence. `exact-recipient-net` means the canonical recipient net credit equals the provider report; it claims that transaction/recipient combination without claiming a single output. `near-omni-output` includes a withdrawal-bound `allocation` with an exact `outputIndex` and fee amounts. `tracking.received` is the verified actual BTC amount in decimal units; `bitcoinPayout.amountMinor` is in satoshis. `provider.receivedAmount` and `bitcoinPayout.providerAmountMinor` preserve the original provider report. A resolved adjustment is no longer listed in `settlementDiscrepancies`.

Existing eligible nonterminal payments use the same scheduled or manual refresh. Missing evidence or a conflicting/legacy payout claim keeps the existing payment pending; never fund a replacement to clear it. Terminal historical records are not rewritten. This release covers one withdrawal and one winning payout through the known connector path. Replacement (RBF) transactions and multiple withdrawals in one batch remain pending. It covers delivery claims, not global refund attribution or monitoring for deep reorganizations after completion.
## Refresh an expired approval review

New mainnet EVM `approval` and `approval-reset` actions have a **15-minute review window**. This is the window to review, sign and submit the permission transaction, not a deadline for it to reach finality. Once submitted, keep tracking its hash even if that review window has elapsed. New transfer, burn and customer-signed mint actions have a separate **five-minute review window** across the released providers. CCTP testnet uses the same windows. Existing saved actions retain their original expiry; always use the returned `expiresAt`.

For a saved approval in `ready` or `signed` state, inspect `reviewRefreshEnabled`. If it is true, your backend may request a fresh review on the same payment:

```http
POST /v1/payments/{id}/transaction/review
Authorization: Bearer YOUR_ROUTE_API_KEY
Idempotency-Key: SAVED_UUID_FOR_THIS_REVIEW
Content-Type: application/json

{"authorizationId":"SAVED_APPROVAL_ACTION_UUID"}
```

Required scope: `payments:write`. The example client exposes `refreshApprovalReview(paymentId, authorizationId, idempotencyKey)`. Reuse the same key if the request times out; use a new key for a later review.

Route checks whether the saved hash is known to the network, both confirmed and pending wallet nonces, the exact token/spender/amount, current allowance, transaction simulation, gas balance, saved fee budget, owner policy and inherited API-key limits. RPC failures block the review. The permission check does not depend on the old transfer quote or its ETA.

A successful response keeps the **same action ID, transaction bytes, signature, hash and reserved nonce**, and returns a new `review`, `assessment` and effective `expiresAt`. `originalExpiresAt` preserves the original deadline. It does not broadcast, change the gas price, relax a minimum output or create another payment. Show the refreshed assessment. If already `signed`, require confirmation before `/execute`; if `ready`, sign and save the exact action first. A saved EVM signature remains valid only for those unchanged transaction bytes.

`nextAction: "refresh-approval-review"` identifies an expired approval eligible for these checks. Eligibility is not a guarantee of success. If the hash is already known (`APPROVAL_ALREADY_BROADCAST`), use `/refresh` to track it. If the nonce changed (`APPROVAL_NONCE_CHANGED`), permission changed (`APPROVAL_STATE_CHANGED`) or the saved gas budget no longer suffices (`TRANSACTION_FEE_CHANGED`), reconcile the wallet; do not create a replacement automatically. Unsigned EVM burn/transfer actions also support the same endpoint when `reviewRefreshEnabled` is true and `nextAction` is `refresh-source-review`; no source signature, hash, submission or wallet handoff may exist. The transaction bytes remain unchanged and fresh simulation and policy checks must pass. Stellar source actions, signed protocol funding actions, submitted approvals and uncertain submission attempts require reconciliation. This is a mainnet endpoint.

After one successful canonical approval confirmation, request the next action on the same payment. Route obtains fresh terms from the selected provider and checks the saved recipient minimum and policy again. Worse terms are blocked; a different provider or explicitly changed payment terms require a new comparison and review.

## Unused drafts and payment history

Mainnet `GET /payments?view=active&limit=50` returns top-level payments, with `nextCursor` and `hasMore`. Pass the returned cursor to retrieve the next page. `view` accepts `active` (default), `archived`, or `all`; `limit` accepts 1–100. A composed payment appears once; inspect its workflow for the individual transfers.

`POST /payments/{id}/archive` takes `{ "version": CURRENT_PAYMENT_VERSION }`. Only unused standalone drafts are eligible. Inspect `canArchive`; a prepared payment, composed parent or composed leg cannot be archived. Archived records return `archivedAt`, `archiveReason` and `archivedBy`. Automatic cleanup archives eligible drafts 24 hours after quote expiry; it may run shortly after that threshold. It retains the record, quote evidence and retry history. Archived drafts cannot be prepared; request a fresh quote for a new payment.

Archival never cancels a provider deposit, removes a signed action, releases a spending reservation or stops tracking. These filters and archive operations currently apply to mainnet payment records; the isolated testnet contract is unchanged.

See [usage and payment reporting](usage-and-billing.md) for processed-USD totals, logical operation metering and stablecoin credit purchases.

## Retries, limits and errors

Persist an operation UUID with your order before sending a quote or payment request. Retry identical input with that same `Idempotency-Key` after an interrupted connection. Matching retries return the original record, including after key rotation; conflicting input returns `409 IDEMPOTENCY_CONFLICT`. A missing or non-UUID key returns `400 IDEMPOTENCY_REQUIRED` and changes nothing; persist a UUID and retry. The exceptions are NEAR source renewal, NEAR deposit review and NEAR captured quote-review acceptance (`/transaction/renew`, `/transaction/review` and `POST /quote-review` on a NEAR payment), which still answer a missing or non-UUID key with `404 NOT_FOUND`; that 404 does not mean the payment is gone. A new quote needs a new UUID. A replayed expired quote is returned with `expired: true`, not silently refreshed. Keys are namespaced by project and operation, separately from dashboard requests.

The preview allows 120 customer requests per project per minute. Each API key can also create 12 quotes and 12 payments per minute, separately from its issuer's other keys and dashboard use. A quote counts only once it passes validation and the policy and key allow an enabled provider. NEAR provider preparation allows six attempts per project per minute; status refresh allows 12 per payment per minute. Authenticated customer responses carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds until the window resets) for the project budget. A rate-limited `429` carries `Retry-After`: the seconds until the limit that refused it resets. Other limits can still refuse the retry. Ordinary JSON bodies are limited to 8,192 bytes. `PUT /policy` accepts up to 131,072 bytes (see [routing policies](routing-policies.md)). `POST /payments/{id}/transaction/signature` accepts up to 110,000 bytes; its `signedTransaction` string is limited to 100,000 characters. Errors have `{ "error": { "code": "…", "message": "…", "details": {} } }` and an `X-Request-Id` header. `details` is optional. Preserve it when forwarding errors to your frontend; NEAR readiness errors include named `blockers`, `checkedAt` and a recovery `action`. See [signed-transaction recovery](payment-transactions.md#when-submission-checks-block-a-signed-transaction). Responses are never cacheable. Use the request ID when reporting failures; do not log keys or credential bodies. Typical statuses: 400 invalid input, 401 invalid/expired/revoked key, 402 insufficient available Route credits, 403 missing scope, 404 inaccessible record, 409 state/expiry conflict, 415 JSON required, 429 rate limit, 503 temporarily unavailable.

The machine-readable OpenAPI contract is available at `/docs/openapi.json`. Additional quote evidence fields can be added; clients should ignore fields they do not recognize.

### Wallet handoff with a missing response

A pending transaction report (`lastError.reason: "reported-transaction"`) keeps normalized `next-action` guidance at `review-required` with no automatic operation. Preserve the `authorizationId` and `transactionHash` from the original report and use that pair when retrying recovery. The current action can display evidence from an earlier action of the payment; do not substitute its authorization ID. A pending cancellation needs another recovery check after finality, not payment confirmation. Explicit confirmation retries for an original wallet transaction still use that original handoff's authorization ID and hash.

If `walletBroadcast.state` is `awaiting-transaction`, Route has reserved wallet submission but has no verified transaction hash. Recover the hash from wallet activity and call `/transaction/broadcast-confirm` for the same authorization. A transient `WALLET_TRANSACTION_NOT_FOUND` permits retrying confirmation, not sending again. An expired review does not prevent confirmation.

If the hash was lost, call:

```http
POST /v1/payments/PAYMENT_ID/transaction/broadcast-recover
Authorization: Bearer YOUR_ROUTE_KEY
Content-Type: application/json

{"authorizationId":"SAVED_ACTION_ID"}
```

An optional `transactionHash` lets Route inspect a known original or cancellation transaction. Keep `Route-Network: testnet` for testnet. Both recovery operations require `payments:write` and the same project/policy access. The SDK methods are `recoverWalletBroadcast(id, authorizationId, transactionHash?)` and `cancelWalletBroadcast(id, authorizationId, cancellationHash)`.

| Recovery `state` | Next step |
| --- | --- |
| `original-verified` | Route registered the exact original transaction. Read `next-action` and track normal receipt/finality/delivery |
| `altered-payment` | The transaction is the exact payment call (sender, contract, calldata, value and chain) with a changed nonce, fee or transaction type. Route records it for review and returns its hash, but never attaches it or releases the nonce, credits or API-limit holds. `canClose` stays `false`. Review the recorded hash; never resend |
| `equivalent-execution` | The hash is a transaction this wallet signed, sent or still queued at any nonce, that is the same payment without being the saved call: the call re-quoted (another fee or finality threshold, the same token, amount, destination and recipient), another value, another envelope type (including blob and set-code transactions), a call to the payment contract Route cannot decode, or any call to the payment's provider contracts or deposit address. For a NEAR source transaction the rule reads the preparation's 1Click deposit: any transfer of the source token, or native send, to the deposit address. Route records it for review exactly like an altered payment: it is never attached, nothing is released, `canClose` stays `false` and the saved call is never submitted or handed to the wallet again |
| `unclassified-sender-transaction` | The hash is a transaction this wallet signed (its signature recovers to the sender), at any nonce (the saved one, a later one, or an earlier one still pending), sent or still queued, in any envelope (including set-code transactions and legacy transactions signed without replay protection), that is neither the saved transaction nor a recognized cancellation, and that Route cannot classify as the same payment: for example the saved call wrapped in the wallet's own delegated or batched execution, the payment sent through another contract of the provider or another bridge, or an unrelated transfer. Route cannot establish that it is not this payment, so it records it for review exactly like `equivalent-execution` (`reason: "unclassified-sender-transaction"`): it is never attached, nothing is released, `canClose` stays `false`, and the saved call is never submitted or handed to the wallet again |
| `unclassified-transaction` | The network shows the reported transaction, but Route cannot verify that this wallet signed it: another account sent it (a relayer or bundler executing through the wallet's delegated EIP-7702 or ERC-4337 code), or its envelope carries no sender signature Route can rebuild (an OP-stack deposit `0x7e`, an Arbitrum system type, an unsigned or unknown type). Who signed it does not matter: it may be this payment executed from the wallet's account. Route records it (`reason: "unclassified-transaction"`, with `observedFrom`, `signer`, `contract`, `observedType` and `transactionData`) and it blocks the payment exactly like `unclassified-sender-transaction` |
| `reported-transaction` | Route recorded the hash you reported for review before reading it, and the network does not show it yet (or it is the saved call sent without a handoff). It blocks the payment until a later check classifies it (the same record is updated) or shows it is this action's own transaction or a finalized recognized cancellation (the record is then cleared). Retry recovery with the same hash; never resend |
| `not-observed` | Returned only when no hash was supplied: no transaction was established. Preserve the action and reservations; this does not prove that the wallet never sent |
| `pending-nonce` | The node reports pending nonce use. Obtain the hash from wallet activity, then recover or confirm it |
| `cancellation-pending` | A cancellation was observed but is not final. Wait and recheck the same hash; the original transaction can still win. Until a check sees it finalized, the report of its hash keeps blocking the payment |
| `cancellation-verified` | A finalized cancellation invalidated the action. If `canClose` is true, explicitly close it below |
| `closed` | The unfunded payment is cancelled. Its immutable history remains; use a fresh quote and new payment for a later attempt |

`POST /transaction/broadcast-confirm` reports the same case as `409 WALLET_TRANSACTION_REVIEW_REQUIRED`, with the evidence in `error.details` (`transactionHash`, saved `nonce`, `observedNonce`, `observedType`, observed gas and fee fields, `checkedAt`, `automaticAttachmentAllowed: false`). A reported hash that is the same payment in another transaction, or any other transaction the wallet signed that is not the recognized self-cancellation of the saved nonce (the `equivalent-execution` and `unclassified-sender-transaction` cases above), returns the same `409` with `reason: "equivalent-execution"` or `reason: "unclassified-sender-transaction"` and is recorded the same way; an existing handoff then no longer presents its `params`. Either way the evidence is kept permanently and saved as the transaction's `lastError`. `paymentState` is `REQUIRES_ATTENTION` and `next-action` is `review-required` until a verified terminal receipt. Confirming the exact original later registers it for tracking but keeps the review, because both transactions may have executed. Route also never sends a saved call again for any action of that payment (payment scope, below), neither by submitting a saved signature nor by handing it to the wallet, for protocol actions, NEAR source transactions and testnet actions alike and whatever the account nonce shows (the altered copy may be queued at a later nonce while the saved one is still next): `POST /execute` and a new `POST /transaction/broadcast` handoff return `409 WALLET_TRANSACTION_REVIEW_REQUIRED` with the recorded evidence before any network read or send, the action keeps its state and `lastError`, the handoff returns no `eth_sendTransaction` params, and the database refuses the same submission or handoff. An existing handoff is still returned and nothing is sent, but with `walletBroadcast.method: null`, empty `params` and a review `notice`. The transaction views stop offering these steps: `walletBroadcastAvailable`, `signingEnabled` and `submissionEnabled` are `false`, and mainnet protocol actions and GET NEAR transaction carry `actionBlocker.code: "WALLET_TRANSACTION_REVIEW_REQUIRED"` with the evidence in `details`. The handoff of an altered spending approval is not refused and keeps its `params`, because that approval moves no funds; its saved signature is still never submitted (`submissionEnabled: false`). A recovery running at the same time either commits first, so the submission or handoff is refused, or waits until it is saved. If the evidence is recorded while Route's own send of the saved transaction is in flight and that send's outcome is uncertain, the evidence still becomes the `lastError`, so `next-action` stays `review-required`, and it carries the send's `SUBMISSION_UNKNOWN` diagnostic as `lastError.submission`.

Route freezes a payment on every transaction reported for it. The threat model: Route never itself submits a saved signature, or starts a new wallet handoff, for **any** action of a payment (the same preparation: its approval and burn or transfer, a NEAR source transaction, the testnet actions of one payment) once any transaction has been reported for **any** action of that payment that is not (a) exactly that action's own saved transaction, (b) a recognized cancellation at its saved nonce, or (c) for a spending approval, a copy with byte-identical calldata to the same spender, in any envelope (an approval moves no funds). "Reported" means a hash given to `broadcast-recover`, `broadcast-cancel`, `broadcast-confirm`, the unused-action close, close after manual check or a saved-signature or composed source cancellation, on mainnet or testnet, for NEAR source transactions, protocol actions and testnet actions alike, on every EVM network the action allows. Who signed the transaction does not matter (the sender, a relayer or bundler, an L2 system deposit, an unsigned or unknown envelope), nor does the network or the flow.

The report is recorded first, for the whole payment, before Route reads the network or checks whether the payment could be closed, and only an authorized caller of the payment can report. The hash is then inspected: it becomes `altered-payment` for the byte-identical call, `equivalent-execution` when the payment's same-payment rule matches, `unclassified-sender-transaction` for anything else the wallet signed and `unclassified-transaction` for anything it did not verifiably sign; or it is cleared, because it is the action's own saved or wallet-confirmed transaction (registering the wallet's exact original still works) or a finalized recognized cancellation. A recognized cancellation is the zero-value, empty-data self-transfer at the saved nonce and, for the unused-action close only, a finalized transaction at the saved nonce that neither calls, is logged by nor pays the payment's provider contracts or deposit address. Only a finalized one clears a report: its receipt is on a finalized block and the finality, provider-log and code checks passed (the database also refuses any other clearance). A cancellation or nonce proof that is not final yet (`cancellation-pending`, or the close's `observedState: "nonce-consumed-pending"`) clears nothing, because a queued transaction at the saved nonce may be the payment itself (for example the payment batched through the wallet's delegated code to its own address); the report keeps blocking until a later check with the same hash sees it finalized. `broadcast-confirm` reads no receipt, so it never clears a report. If the network read fails, or the transaction is not visible yet, the report stays recorded as `reported-transaction` evidence and keeps blocking; a later check with the same hash classifies or clears that same record. Mistyped hashes block too: report only hashes the wallet shows. A testnet action takes reports from `/prepare` on, before `/transaction` has requested its authorization: `broadcast-recover` inspects and records the hash (nothing closes), and while it is open `/transaction` is refused with `409 WALLET_TRANSACTION_REVIEW_REQUIRED` and the database refuses the authorization. On every flow, `broadcast-confirm` for an action without a handoff returns `409 WALLET_TRANSACTION_REVIEW_REQUIRED` with the recorded report in `error.details` (instead of `404 WALLET_BROADCAST_NOT_FOUND`) when the hash is on record for review.

While such evidence is open for any action of the payment, `/execute` of any saved signature, a new `/transaction/broadcast` handoff and a new action (`POST /transaction`; testnet `/prepare` and `/transaction`) return `409 WALLET_TRANSACTION_REVIEW_REQUIRED` with the evidence, and the database refuses the same submission, handoff or new action. No cancellation or closure of any action releases the payment's holds. The action views read the payment's evidence: `walletBroadcastAvailable`, `signingEnabled` and `submissionEnabled` are `false`, and an action with no evidence of its own shows the payment's evidence as its `lastError`, so `next-action` is `review-required`. Only case (c) is exempt: a spending approval's wallet-altered copy refuses only that approval's own submission. The only way to lift the block is Route's audited attribution of that exact transaction to another of the project's payments with matching terms (its saved signed or wallet-confirmed hash, from the same wallet and network), which runs before a protocol close or cancellation. NEAR and testnet evidence has no attribution path: an operator reviews those payments (see the operations notes in the audit research). A legacy signature without replay protection (`v` 27 or 28) is accepted as evidence only, never to attach a transaction or prove a cancellation.

What remains outside this rule is a same-payment copy that nobody reports to Route: one broadcast from another wallet, or one this wallet signed but never reported at any of these endpoints. Route cannot see it unless its provider search finds it (for protocol payments at closure or cancellation), and it cannot see a transaction queued behind a nonce gap in the wallet's own pool until it reaches the network. Report every hash the wallet shows before you close or resubmit.

Two more cases are outside this rule and are known limits. First, each attempt of a composed payment is its own draft: a report for a cancelled attempt's action after that attempt was closed is not recorded (a closed action takes no evidence, and the parent's routes report to the latest attempt), and a new attempt on the parent is prepared and handed to the wallet normally; a NEAR leg is not searched at cancellation. Report every hash before you close an attempt. Second, Route's own submission is serialized at its reservation: a report that is recorded after Route reserved the submission of a saved signature (the action moved to `submitting`) but before its single broadcast attempt does not stop that broadcast. The report is still recorded and blocks every later submission, handoff and action of the payment.

Two guarantees hold for every check that reads the network for an action (the in-progress state below applies to every action of the payment) (the unused-action close and close after manual check, `broadcast-recover`, `broadcast-cancel` and the saved-signature or composed source cancellation). First, while the check runs, submission and a new wallet handoff of that action return `409 WALLET_EVIDENCE_CHECK_IN_PROGRESS` with `details.retryable: true` and `details.retryAfterSeconds`, before any network read, and the database refuses them too. The check clears this state in the same database transaction that records what it found, or as soon as it finds nothing or fails; it also lapses on its own after 2 minutes, so a request that never finishes cannot block the payment for longer. Retry after the check returns. A check that runs longer than those 2 minutes still records what it found, even if a submission or a wallet handoff (or the registration of the wallet's own original) started in between, so the payment then reads `review-required`; it clears only its own state, never a newer check's. A close or cancellation whose check ran longer than 2 minutes closes nothing: it returns `409 WALLET_EVIDENCE_CHECK_EXPIRED` with `details.retryable: true`; retry the same request. Second, what the check observed is recorded even if the caller's API key is revoked, or the member loses access, while it runs: Route records the evidence for the action, audits it as the member who started the check, and only then returns the authorization error. Another credential then finds the evidence and cannot send the payment.

Without a hash, Route searches backwards from recent canonical blocks, then narrows the sender/nonce transition. This avoids requiring years-old archive state to recover a recent transaction. The search is bounded to 40 historical count reads. Pending transactions, pruned history for an older action and unavailable or oversized block responses may still prevent discovery. An incomplete lookup never releases reservations. Supply the wallet hash when available and respect `429` backoff; new recovery checks are limited to five per action per minute. Reading the saved `GET /recovery` report remains separate and makes no chain request.

#### Rejected prompts and confirmed cancellation

A wallet rejection, closed tab or elapsed review does not establish an on-chain cancellation. If you choose to abandon the action, the wallet owner may explicitly authorize a cancellation at the **saved source account, chain and nonce**: a zero-value native self-transfer with empty data. This costs network fees and races any existing transaction at that nonce. Route does not create, sign or broadcast that cancellation. Do not send another provider deposit as a cancellation.

Route accepts a cancellation only after independently verifying the signature and hash, successful canonical receipt, empty code and logs, 21,000 execution gas used (excluding separately reported Arbitrum L1 posting gas), and chain finality. Legacy and type-2 self-transfers are supported for this cancellation proof. This does not enable type-2 payment funding or fee-edited payment transactions. A cancellation on a chain whose finality cannot be established remains unresolved.

Once the cancellation is final, call `POST /payments/{id}/transaction/broadcast-cancel` with `{"authorizationId":"SAVED_ACTION_ID","transactionHash":"CANCELLATION_HASH"}`. This operation verifies the evidence again, closes the unfunded **standalone** payment, and releases unused payment-credit and rolling API-limit reservations in one database transaction.

A cancellation consumes only the saved nonce, and the wallet may have sent the payment itself at another nonce. For a mainnet protocol payment, Route first searches the sender's provider events for the same payment at any other nonce, exactly as for [an unused EVM protocol action](#close-an-unused-evm-protocol-action), and stores that search with the cancellation. A match returns `EVM_CLOSURE_REVIEW_REQUIRED` and is recorded for the action as review evidence (below): from then on Route never submits the saved signature or hands the saved call to the wallet. If Route already recorded that the wallet sent the payment call at another nonce, with changed fees or in another transaction it signed (`lastError.code: "WALLET_TRANSACTION_REVIEW_REQUIRED"`), for a protocol payment or a NEAR source transaction, or the provider cannot be searched by sender (SwapKit native deposits, or a payment call Route cannot decode), recovery returns `canClose: false` and closing returns `WALLET_CANCELLATION_REVIEW_REQUIRED`. The same recorded evidence blocks a hosted-testnet handoff cancellation. In every case the payment and its reservations are kept for review; do not send replacement funds. Repeating it with the same proof cannot release credits twice. Quote charges and network fees are not refunded; cancelled payments add no processed USD volume. The consumed nonce stays recorded and cannot be reused. Old payment/deposit instructions must no longer be used.

Destination mint actions and transfers with verified execution evidence require separate review. A recorded wallet-altered burn or transfer (`altered-payment` or `WALLET_TRANSACTION_REVIEW_REQUIRED`) also blocks closure of that action: `canClose` is `false` and closing returns `409 WALLET_CANCELLATION_REVIEW_REQUIRED` (`SOURCE_CANCELLATION_REVIEW_REQUIRED` or `COMPOSED_CANCELLATION_REVIEW_REQUIRED` for saved-signature and composed sources), with all reservations kept. An altered spending approval moves no funds, so it stays flagged for review but does not block closing an otherwise unfunded payment. Report every hash the wallet shows to `/transaction/broadcast-confirm` before you close with a cancellation. An original transaction confirmed through a browser-wallet handoff prevents that handoff’s cancellation closure. A hash saved with a signature alone is not proof of submission; supported saved signatures use the recovery flow below. For a cancelled first composed EVM source leg, verified closure ends the unfunded parent and releases unused payment holds; obtain a fresh quote for a new payment. See [first-transfer cancellation](composed-payments.md#close-a-cancelled-first-transfer). For the active second or third EVM source leg of a composed payment, a saved signature/hash can be superseded only by a verified finalized cancellation at that exact nonce; see [intermediate cancellation recovery](composed-payments.md#continue-after-an-intermediate-cancellation). The funded parent remains open with its reservations retained. If the original transaction won the race, recovery registers it for tracking instead of declaring the payment cancelled. Original signatures, hashes, spending approvals and provider deposit records are never erased. See [EVM wallet submission](next-actions.md#evm-wallet-submission).

## SwapKit changed terms before preparation

For `SWAPKIT_BUILD_REFUSED`, SwapKit refused the build before opening a channel and the payment's holds are already released. Fix the cause and call `/prepare` again with the same wallets before `providerAllocation.expiresAt`, or cancel the payment. For `SWAPKIT_ALLOCATION_NEEDS_REVIEW`, keep the saved payment. [Read and explicitly accept the captured quote review](providers.md#reviewing-changed-swapkit-terms) when Route can verify the same live deposit channel. The original quote and the accepted revision are retained. Unknown build outcomes, invalid identities, expired channels and existing wallet actions are not eligible for this automatic recovery. Never create another channel merely because preparation returned an error. A failure after the SwapKit claim keeps the payment’s credit and API-key holds and reports `REQUIRES_ATTENTION`. A validated channel resumes with the same wallets only until `providerAllocation.expiresAt`; see [SwapKit failure boundaries](providers.md#failure-and-execution-boundaries).

## Renew an expired customer Stellar mint

When `next-action` returns `renew-mint`, call `POST /payments/{id}/transaction/renew` with the current `authorizationId` and a persisted UUID `Idempotency-Key`. This applies to customer-signed Circle CCTP destination mints on Stellar mainnet and testnet. This mint flow does not renew a source burn or approval; expired Stellar CCTP approvals have their own [approval renewal](#renew-an-expired-stellar-cctp-approval). NEAR Stellar source deposits have the separate [source renewal flow](#renew-an-expired-near-stellar-source) below.

Route first verifies the old signed transaction, a fresh closed Stellar ledger past its finite expiry, and the fee payer's unchanged sequence. It then checks that the CCTP message is still unminted and rebuilds the same invocation within the existing destination fee ceiling. The recipient, mint message, fee payer and sequence stay fixed. A timeout or a missing transaction lookup alone is insufficient.

```js
const renewed = await sdk.renewMint(paymentId, authorizationId, renewalRequestKey);
const next = await sdk.getNextAction(paymentId);
// Present next.action.payload to the wallet for a fresh signature.
```

The endpoint prepares a new authorization; it does not send funds. Retain the original action and store the replacement ID and new signed payload separately. Repeat the same request key if the response is lost. Mainnet returns the replacement transaction directly; testnet returns it under `transaction`. The normal signature and execute operations apply to the replacement.

`MINT_RECONCILIATION_REQUIRED` means the old action is ineligible or chain evidence is insufficient. Already-submitted or uncertain actions, observed receipts and consumed wallet sequences stay in reconciliation. A changed fee estimate must fit the original fee ceiling; renewal cannot raise the customer's budget. Never repeat the source transfer to recover destination minting.

## Renew an expired Stellar CCTP approval

When `next-action` returns `renew-approval`, call `POST /payments/{id}/transaction/renew` with the current `authorizationId` and a persisted UUID `Idempotency-Key`. This applies to a mainnet Stellar-source Circle CCTP `approval` or legacy `approval-reset` whose signed time bound has passed without an included receipt, whether it was unsigned, signed, submitted or uncertain. `approvalRenewalEnabled: true` on the action marks eligibility; it is not permission to replace or send.

Route first verifies the saved envelope and any saved signature. It then checks that the network reports no receipt for the saved hash. A fresh closed ledger must be past the signed `maxTime`, and the wallet's account sequence must still be just below the saved nonce. Only then does it build a fresh action at the **same** wallet sequence, with the ordinary `POST /transaction` checks: fresh wallet, fee, policy and API-key limits. The replacement is one exact approval or, when a live allowance already covers the burn through its expiry, the burn itself. The wallet sequence reservation moves from the old action to the replacement in the same database transaction. Renewal keeps the payment open; to abandon it instead, see [closing an expired Stellar payment](#close-an-expired-stellar-payment).

```js
const renewed = await sdk.renewApproval(paymentId, authorizationId, renewalRequestKey);
const next = await sdk.getNextAction(paymentId);
// Present next.action.payload to the wallet for a fresh signature.
```

Renewal sends no funds. The expired action, its signature, credit and API-key holds are retained, and the payment history shows both actions with an `approval.renewed` event. Repeat the same request key if the response is lost; a different key for the same old action returns `APPROVAL_RECONCILIATION_REQUIRED`. `APPROVAL_RECONCILIATION_REQUIRED` also means renewal is not possible. If the saved approval is already on the network or its sequence was used, Route records that; see [below](#a-wallet-broadcast-the-saved-stellar-action-itself). Otherwise the evidence was insufficient: the sequence was skipped, the chain evidence was unavailable or unverifiable, or the ledger was not yet past the time bound, and nothing changes. In every case, refresh, then follow `next-action`. Never re-sign or rebroadcast the expired approval outside Route.

## Renew or close an expired Stellar burn or transfer

This applies to a mainnet Stellar-source protocol payment (Circle CCTP, LayerZero or Axelar) whose current burn or transfer passed its signed time bound without an included receipt.

- **Never signed** (`state: "ready"`, `sourceRenewalEnabled: true`): `next-action` returns `renew-source`. Call `POST /payments/{id}/transaction/renew` with the `authorizationId` and a persisted UUID `Idempotency-Key`. Route performs the same proof as [approval renewal](#renew-an-expired-stellar-cctp-approval), then builds a fresh action at the **same** wallet sequence with fresh terms and the ordinary checks. For CCTP that is the burn, or an approval first if the allowance no longer covers the burn through its expiry. Sign the replacement; the renewed action stays in the history and is never tracked as the payment's source. A `source.renewed` event records the renewal.
- **Signed, submitted or uncertain**: the payment's source hash is already registered, so renewal is refused with `SOURCE_RECONCILIATION_REQUIRED`. `next-action` returns `close-payment`; see [below](#close-an-expired-stellar-payment).
- **Renewal cannot run**: while execution is paused, or after the owner policy or provider settings changed since the payment was saved (permanent for that payment), renewal is refused. When `closeEnabled` is true, `next-action` returns `close-payment` for an expired approval or never-signed burn or transfer too, and error hints point to `/transaction/close` instead of `/transaction/renew`.

Older saved burns may fail the stricter signature recheck. Once expired, eligible unsigned actions offer `renew-source`; signed actions require proof-gated closure or tracking.

```js
const renewed = await sdk.renewSource(paymentId, authorizationId, renewalRequestKey);
```

### A wallet broadcast the saved Stellar action itself

Route returns the unsigned XDR and expects the signed transaction back at `/transaction/signature`. A wallet can instead sign and broadcast it itself, so a transaction Route never saw may have executed. A Stellar transaction hash excludes signatures, so the saved envelope hash is the landed hash. Before any renewal or closure, Route checks that hash:

- **Found, exactly the saved envelope with the sender's single signature**: the request is refused (`SOURCE_RECONCILIATION_REQUIRED`, `APPROVAL_RECONCILIATION_REQUIRED` or `CLOSE_UNAVAILABLE`). Route then adopts the transaction as the submitted action: `state: "submitted"` with that hash and the landed envelope, a receipt with `included: true` and `finalized: false` until tracking attaches its verified evidence, a registered source hash for a burn or transfer, and a `wallet.broadcast_verified` event. `next-action` becomes `track`; delivery proceeds as for any submitted transfer.
- **Found but not adoptable** (a fee-bump wrapper, signatures Route cannot hold, or an envelope or result that is not verifiably the saved one) with the account sequence independently used, or **not found with the sequence used**: the request is refused and the observation is saved on the action (`receipt.submissionObservation.state: "sequence-consumed"`, bound to the saved envelope hash) with `lastError.code: "STELLAR_SEQUENCE_REVIEW"`. The preparation moves to `state: "needs-review"`, so `paymentState` is `REQUIRES_ATTENTION` and the webhook outbox receives `payment.requires_attention` once. The review is permanent: a later refresh can clear `lastError`, but not the review state or the saved observation. `next-action` returns `review-required`; renewal and closure are no longer offered.
- **Anything else unverifiable** (a found receipt whose account sequence is still unused, unavailable RPC or account evidence, or a sequence gap): nothing is written.

None of these sends, renews or releases anything. Recovery reports never state that such an action moved no funds before this check.

## Close an expired Stellar payment

`POST /payments/{id}/transaction/close` with the current `authorizationId` and a persisted UUID `Idempotency-Key` closes an unfunded **standalone** Stellar-source protocol payment whose current approval, burn or transfer passed its signed time bound without an included receipt. `closeEnabled: true` marks eligibility. `next-action` suggests it as `close-payment` only when renewal is impossible: the action is signed, submitted or uncertain, or renewal cannot run because execution is paused or the payment's saved settings changed. The app may also offer it to abandon an expired approval or unsigned transfer.

Route proves the transaction can never execute: the saved envelope and any signature match, the network reports no receipt for its hash, a fresh closed ledger is past its `maxTime`, and the account sequence is still just below its nonce. In one database transaction it then:

- records the proof on the action (`closure`), which becomes immutable;
- releases the action's wallet sequence reservation, so other payments from the same wallet no longer receive `WALLET_SLOT_RESERVED`;
- releases unused API-key spending holds and the payment credit hold exactly once;
- marks the payment `cancelled` (`nextAction: "cancelled"`, `lastError.code: "STELLAR_ACTION_EXPIRED"`) with `transaction.closed` and `draft.cancelled` events;
- sets the terminal `tracking.status: "closed"` (`sourceVerified: false`), because no receipt is awaited any more. The webhook outbox receives `payment.cancelled`, never a later `payment.requires_attention`.

```js
const closed = await sdk.closePayment(paymentId, authorizationId, closeRequestKey);
// closed.nextAction === 'cancelled'; obtain a fresh quote for a new payment.
```

Closing sends and signs nothing. Execution does not need to be enabled. A closed payment cannot be resumed. An allowance granted by an earlier approval is not revoked: the messenger can only spend it inside a burn the wallet itself authorizes, and it expires at its own ledger. Repeat the same key if the response is lost; a missing or non-UUID `Idempotency-Key` returns `400 IDEMPOTENCY_REQUIRED`. `CLOSE_UNAVAILABLE` means the payment cannot be closed. If the saved transaction is on the network or its sequence was used, Route records that as described [above](#a-wallet-broadcast-the-saved-stellar-action-itself). Otherwise nothing changes: the payment is a composed leg, the sequence was skipped, the source was verified, or the ledger evidence was unavailable, stale or not yet past the time bound. A consumed or skipped sequence still needs operator review, like any missing receipt.

## Missing Stellar receipts

Refresh the existing payment to check its saved transaction hash. For NEAR Stellar source payments, Route-built Stellar protocol actions on mainnet and customer-signed CCTP mints on testnet, missing receipts include `submissionObservation`. NEAR preparations expose it under `tracking.submissionObservation`. The recovery report exposes it under `evidence.transactions[]`; protocol transaction receipts also include it. Testnet payment tracking includes it directly.

| State | Meaning | Next step |
| --- | --- | --- |
| `not-observed` | The receipt was not found; the account sequence is immediately before this transaction and its network expiry has not passed. This does not establish mempool acceptance. | Continue refreshing the same payment. |
| `expired-unobserved` | A fresh closed ledger is past the envelope’s finite expiry and its sequence remains unused. | Follow `next-action`. An eligible customer mint may offer `renew-mint`, an expired Stellar CCTP approval `renew-approval`, an unsigned Stellar protocol burn or transfer or an eligible NEAR Stellar deposit `renew-source`, and a signed Stellar protocol burn or transfer `close-payment`. Other actions require recovery review. |
| `sequence-consumed` | The account sequence has reached or passed the saved transaction sequence, but its receipt is unavailable. A Stellar renewal or close check also records it for a never-signed action, bound to the saved envelope hash. | Recover the original receipt before continuing. Sequence use alone does not establish success, failure or a refund. |
| `sequence-gap` | The account sequence is behind the sequence immediately preceding this transaction. | Review earlier wallet actions without creating a replacement payment. |
| `unavailable` | Fresh network or account evidence could not be verified. | Retry refresh. Do not treat the missing evidence as expiration or failure. |

`checkedAt` dates the observation. `closedAt` is the verified ledger close time used for expiry; `ledger` identifies the account-read ledger. `expiresAt` comes from the Stellar envelope, independently of Route’s review deadline. Observations are saved evidence and may become stale.

Stellar RPC retains a bounded transaction history, so a missing receipt alone cannot establish that a transaction never executed. See [Stellar’s transaction lookup reference](https://developers.stellar.org/docs/data/apis/rpc/api-reference/methods/getTransaction).

These observations do not resend transactions, close payments, release wallet sequences or credit/spending reservations, or change processed payment totals. Keep the original payment and authorization IDs. Only an explicitly offered recovery operation can authorize a continuation; it performs its own fresh checks.

## Bitcoin and Solana source recovery

Refresh the existing payment to update `preparation.tracking.sourceSubmissionObservation`. For transactions signed by your wallet and saved in Route, the recovery report also exposes this as `evidence.transactions[].sourceSubmissionObservation`. This covers NEAR Bitcoin, Solana and Fogo sources and SwapKit Bitcoin sources. Route does not hold the wallet key: the saved signature is the one your wallet previously supplied.

| State | Meaning | Next step |
| --- | --- | --- |
| `not-observed` | The original transaction is not visible in the queried evidence. | Refresh the same payment. Absence is not proof that it never executed. |
| `pending` | The original transaction or signature is reported, without a verified final source receipt. | Wait and refresh; do not sign or send again. |
| `receipt-reported` | A transaction receipt or spend by the original hash is reported. | Continue receipt verification. This observation alone does not establish funding or delivery. |
| `inputs-conflicted` | Another Bitcoin transaction references a reserved input. `conflictingHashes` identifies the observed candidates. | Use the explicit Bitcoin recovery operation below to verify the outcome. This observation alone cannot adopt a hash or release holds. |
| `blockhash-expired` | The Solana/Fogo signature was not found, the blockhash is invalid at finalized commitment, and finalized height is past the saved last-valid height. | Follow `next-action`; eligible NEAR sources offer `renew-source`. Retain the original payment and signatures. |
| `failure-reported` | Solana/Fogo reports an error for the saved signature. | Verify the finalized transaction result and fees before deciding how to recover. |
| `unavailable` | Network, freshness or transaction evidence could not be verified. | Retry refresh. Outages and restricted reads never establish expiry or failure. |

`checkedAt` records when Route observed the evidence; it is not a guarantee of current mempool state. Bitcoin reports are tied to the saved signed transaction and exact input references. A conflicting transaction might still be unconfirmed or later disappear. Solana history lookups can be incomplete even with `searchTransactionHistory: true`; expiry does not establish a past refund or non-execution.

`next-action` returns `review-required` for conflicts, blockhash expiry or reported errors. Existing verified source/destination receipts take precedence. These checks do not rebroadcast, replace hashes, cancel payments, release credit or spending holds, or increment processed USD totals. Keep refreshing the original payment while its receipts are reconciled.

References: [Esplora transaction and outspend API](https://github.com/Blockstream/esplora/blob/master/API.md), [Solana signature status](https://solana.com/docs/rpc/http/getsignaturestatuses), [Solana blockhash validity](https://solana.com/docs/rpc/http/isblockhashvalid).

## Intermediate funds in a composed route

Read the parent payment's `workflow.recovery.intermediates` for the exact received asset, amount, network, wallet and receipt hash. These records do not assert a current spendable balance. If the parent offers `advance`, use [continuation preview and review](composed-payments.md#review-an-interrupted-route) before preparing the next transfer. If a child already exists, continue or reconcile that saved child. Do not create another root payment to retry an uncertain intermediate transfer.

For a composed route with an expired remaining preview or a finalized intermediate refund, follow [reviewed continuation](composed-payments.md#refresh-an-unfunded-transfer). Refreshing estimates never authorizes replacing an uncertain signed transaction.


## Close an unused EVM protocol action

An EVM transaction has no on-chain expiry. An expired Route review, a missing receipt, or the absence of a saved signature does not prove that the wallet cannot submit the transaction later. To abandon an unfunded standalone protocol payment, read its current transaction. This workflow is available only when `closeEnabled: true` and `closureRequirement: "finalized-nonce-cancellation"` are returned, with no saved signature, submission attempt or wallet handoff. Both appear only after the action's review expires (`expired: true`); until then the next step is signing, and an early close returns `EVM_CLOSURE_UNAVAILABLE` with `details.expiresAt`. It covers the current approval, approval reset, burn or transfer; it does not close a destination mint or a composed payment. The dashboard offers the same workflow as **Close this unused payment**.

1. Review the saved transaction's network, sender and nonce with the wallet owner. Closure needs one finalized transaction from that sender, on that network, at that exact nonce, other than the payment itself:
   - If the wallet already used the nonce for another transaction, such as an ordinary transfer after the payment was abandoned, use that transaction. It may have succeeded or reverted.
   - Otherwise the owner may send a zero-value transfer to its own address with no call data at that nonce. This separate transaction consumes native gas and needs the owner's explicit authorization. Route does not build, sign or send it automatically.

   Do not send the original payment as part of this recovery. A transaction that repeats the payment call with changed fees, or that calls, is logged by or pays the payment's provider contracts or deposit address (for example a token transfer to a SwapKit deposit address), is never accepted: it may be the payment itself. Keep the payment and its reservations for review, preserve that hash and do not send replacement funds.
2. Wait for the transaction's canonical finality. A pending transaction, one confirmation alone or a wallet's success label is insufficient. Preserve its transaction hash.
3. Call `POST /payments/{id}/transaction/close` with `payments:write`, a persisted UUID `Idempotency-Key`, and the saved authorization ID plus that hash:

```json
{"authorizationId":"SAVED_AUTHORIZATION_ID","cancellationHash":"0xFINALIZED_TRANSACTION_HASH"}
```

```js
const closed = await sdk.closePayment(paymentId, authorizationId, closeRequestKey, finalizedTransactionHash);
```

Route rebuilds the transaction from its signed fields and recovers the sender, then verifies the network, exact nonce, finalized receipt, the sender's finalized nonce count and, for anything other than a zero-value self-transfer, that it does not touch the payment's provider contracts or deposit address. It rechecks that the original payment has no funding evidence.

A consumed nonce proves only that the payload signed at that nonce can never execute. The wallet owner may have sent the same payment at another nonce, and many wallets choose their own nonce. Before closing a burn or transfer, Route therefore searches the sender's own provider events, from a block before the payment was first revealed (the preparation, which already returns the deposit instructions, a SwapKit build attempt's deposit address, or the action itself, whichever is earliest, less a 10-minute clock margin) through the current block, for the same token, amount, destination and recipient:

| Provider | Searched event | Match |
| --- | --- | --- |
| Circle CCTP | `DepositForBurn` from the saved messenger, by `depositor` and burn token | Amount, destination domain and mint recipient; for Stellar, the recipient declared in the forwarder hook |
| LayerZero OFT | `OFTSent` from the saved OFT, by `fromAddress` | Destination endpoint, then that transaction's own `send` calldata for the recipient and the amount after dust removal at the OFT's conversion rate from the saved quote (a dust-free re-send is the same payment; another amount is not). A send that cannot be decoded counts as a match |
| Axelar ITS | `InterchainTransfer` from the saved service, by token ID and `sourceAddress` | Destination chain, destination address and amount |
| Allbridge | Source-token `Transfer` from the sender to the saved bridge contract | That transaction's own `bridge` calldata: amount, destination chain and recipient (the relayer fee may differ). A transaction that is not a direct, decodable `bridge` call counts as a match |
| SwapKit ERC-20 deposit | Token `Transfer` from the sender to the saved deposit address | Any nonzero amount, since the address belongs to this swap |

A transaction Route already saved for another of the project's payments (its signed or wallet-confirmed hash from the same wallet), or a Stellar burn whose forwarder hook names another of the project's payments, belongs to that payment and does not block closure. A hook's payment ID counts only when that payment is the same one: a Stellar-destination Circle payment with the same amount and payee from the same source network and sender, or a payment with a saved action whose calldata is exactly the found transaction's. A hook naming any other payment, or one Route never prepared, attributes nothing; the search lists it in `equivalentScan.attributedTransactions`. Recorded `equivalent-execution` evidence is re-attributed on every later close or cancellation attempt: if Route has since tied that transaction to another of its payments, the evidence is resolved and the search runs again. The database checks that attribution itself and records it with the evidence, which stays on record; the resolution is audited. The action's `lastError` then shows any other evidence still unresolved, or otherwise `code: "EQUIVALENT_EVIDENCE_SUPERSEDED"`. A wallet-altered copy of the saved call is never resolved. Any other match returns `EVM_CLOSURE_REVIEW_REQUIRED` with `details.observedState: "equivalent-execution"` and `details.evidence`. Route records the evidence in its append-only evidence journal and saves it on the action as `lastError` with code `WALLET_TRANSACTION_REVIEW_REQUIRED`, as for an altered payment, in one transaction. The payment reads `REQUIRES_ATTENTION`, closure is withdrawn (a later close returns `observedState: "review-recorded"`), and its wallet reservation, API-limit holds and payment credit stay reserved for review. Route also never submits the saved call or hands it to the wallet afterwards, whatever the account nonces show: after a review refresh and a new signature, `/execute` and a new `/transaction/broadcast` still return `409 WALLET_TRANSACTION_REVIEW_REQUIRED` (see [payment transactions](payment-transactions.md)), because a copy the wallet queued at a later nonce could execute as soon as the saved nonce is used. A SwapKit native-asset deposit emits no event, and a saved payment call Route cannot decode cannot be matched, so those payments cannot be searched: `closeEnabled` stays `false`, and a close request returns `EVM_CLOSURE_REVIEW_REQUIRED` with `equivalent-scan-unsupported`. A workspace member can still [close them after a manual check](#close-after-manual-check). If the search cannot complete, Route returns `equivalent-scan-incomplete`, records no evidence and releases nothing. Causes include pending wallet transactions, a transaction the wallet sent or queued while the search ran, a head block that changed during the search (these three carry `details.retryable: true`: retry once the wallet is idle), and an RPC failure or provider limit. An attempt that saved progress (below) also carries `details.retryable: true`. One attempt reads at most 256 log pages (2,000 blocks per page; 50,000 on Arbitrum) and, for LayerZero, 96 candidate transactions (192 for the oldest unsearched blocks) in 45 seconds. For an older action or a busy wallet, Route saves the contiguous searched range through the block that was finalized when the attempt started, and the message names the block it reached; retry the same request and the search continues from there until it reaches the current block. Approvals move no payment funds and need no search.

The search cannot see a transaction queued behind a nonce gap in the wallet's own pool until it reaches the network, nor a token transfer that another account makes later from this wallet's allowance. Keep the wallet idle while closing.

Success returns the closed preparation; payment reads report `paymentState: "CANCELLED"`, transaction reads preserve `closure.proof` (`proofType` `self-cancellation` or `nonce-consumed`, with `equivalentScan` naming the searched contract, sender and block range), and `next-action` returns `cancelled`. Only that payment's unused wallet reservation, API-limit holds and payment-credit hold are released. Existing quote charges, network fees, token allowances and transaction history are retained.

Retry a lost response with the same request key and body. `EVM_CANCELLATION_REQUIRED` means a qualifying finalized transaction has not been established. Without `cancellationHash`, its `details` name the `network`, `sender` and `nonce` to use; with one, `details.observedState` reports what Route found and the message says what to do: `nonce-consumed-pending` or `cancellation-pending` means the transaction is not final yet, so retry the same hash later; `not-observed` means the network does not report it; `original-verified` means it is the payment itself, so keep the payment for review and do not send replacement funds. `EVM_CLOSURE_REVIEW_REQUIRED` means the same payment was found, or could not be ruled out, at another nonce, as described above. With `observedState: "altered-payment"`, the hash repeats the payment call with changed fees, even if it is only queued behind the unused saved nonce; that evidence is recorded like a match, with the same refusals of submission and handoff. With `observedState: "equivalent-execution"` and a hash you supplied, the hash is the same payment signed by the wallet in another transaction (re-quoted, another value or envelope type, or a call to the payment's provider contracts), sent or still queued at any nonce; it is recorded the same way. With `observedState: "unclassified-sender-transaction"`, the hash is another transaction the wallet signed that is not a nonce proof (it calls, is logged by or pays the payment's provider contracts or deposit address at the saved nonce, or it is at another nonce); it is recorded the same way, for an approval too. An altered spending approval moves no funds: it is recorded and its saved signature is never submitted, but the approval can still be closed with a finalized transaction at its saved nonce (`closeEnabled` stays `true` once its review expires); the same payment found at another nonce still blocks it. `EVM_CLOSURE_UNAVAILABLE` means the saved action does not qualify, or its review has not expired yet. None of these errors permits releasing holds, recreating the payment or sending replacement funds. Read and reconcile the existing payment first. A fresh quote and payment may be created after verified closure. Actions with a saved signature or wallet handoff use their separate recovery below; never submit them through this unsigned-action workflow.

### Close after manual check

When Route cannot close an unused EVM payment on its own, because it cannot search the wallet for the same payment at another nonce (for example a SwapKit native-coin deposit) or its search cannot complete (for example when the network provider keeps failing on the same blocks), `next-action` says so. Transaction reads report `manualCloseEnabled: true` for every expired unsigned burn or transfer, since Route learns whether its search completes only when it runs. A workspace member can then close it **in the dashboard only** with **Close after manual check** on the payment detail. It is not available to API keys: `POST /api/projects/{projectId}/routing/payment-drafts/{paymentId}/transaction/close-attested` refuses API-key requests with `403 DASHBOARD_SESSION_REQUIRED`, and the `/v1` API does not expose it.

The request needs a session with the `payments.prepare` permission and a sign-in within the last 15 minutes (otherwise `403 FRESH_SIGN_IN_REQUIRED`), a UUID `Idempotency-Key`, and `{authorizationId, cancellationHash, confirmNoEquivalentPayment: true, reason}`, where `reason` (10–500 characters) says how the member checked. Route still requires the finalized transaction that consumed the saved nonce, exactly as above, and still refuses a payment for which it recorded evidence that the payment may have executed (an altered payment or the same payment at another nonce). Where it can, Route runs its own search first: a clear search closes the payment on Route's own proof, a match is recorded and refuses, and a search that a retry may complete (`retryable`, including an older payment's search that saved progress and continues on the next attempt) refuses. Only otherwise does the member's attestation close the payment.

Route does not verify the manual check. The closure proof has `proofType: "manual-attestation"`, the verified `nonceProofType`, `equivalentScan.status` `unsupported` or `incomplete` with Route's reason, and `attestation` (member, session, time, reason and statement). The database accepts it only for a member with the permission, a session signed in within 15 minutes, and a fresh attestation. Holds are released and events emitted exactly as for any other closure; the preparation's `lastError.code` is `EVM_ACTION_CLOSED_AFTER_MANUAL_CHECK`, and the audit log records `payment.evm_action_closed_after_manual_check` naming the member. If the check was wrong and the payment executed, it is no longer tracked as open: check carefully, and never send replacement funds for a payment that may have executed.

## Close a cancelled signed source transfer

For a standalone mainnet payment with a saved EVM source signature on Ethereum, Base or Arbitrum, use the same recovery endpoints as wallet handoffs. This covers signed actions that were never submitted and actions with an uncertain submission result. Route preserves the original signature, hash, provider allocation and consumed nonce; it does not reset the action or sign a cancellation.

1. Call `POST /payments/{id}/transaction/broadcast-recover` with `authorizationId` and the cancellation `transactionHash`. Route verifies the exact source account, network and nonce against a finalized, zero-value self-transfer with no call data. If the original transaction won instead, continue tracking its saved hash.
2. When `canClose: true`, explicitly call `POST /payments/{id}/transaction/broadcast-cancel` with the same authorization ID and hash. The cancellation fee and its USD valuation must be available for the saved evidence. Route also searches for the same payment sent from the wallet at another nonce, as in [the handoff cancellation](#rejected-prompts-and-confirmed-cancellation); a match, recorded evidence or an unsearchable provider keeps the payment for review.
3. Closure returns `state: "closed"` and `paymentClosed: true`. Unused payment-credit and rolling API-limit holds are released once. `/next-action` returns `cancelled` and `/recovery` returns `closed`. Quote charges and network fees remain unchanged.
4. If a new transfer is needed, obtain a fresh quote and create a new payment with a new request key and wallet authorization. Never reuse the old signed action or deposit instructions.

The SDK methods are `recoverWalletBroadcast(paymentId, authorizationId, transactionHash)` and `cancelWalletBroadcast(paymentId, authorizationId, transactionHash)`, including when your backend owns the signing key. These calls do not require a browser wallet.

A pending cancellation, missing receipt, expired review, conflicting funding evidence or committed spending limit retains the existing payment and holds. `SOURCE_CANCELLATION_REVIEW_REQUIRED` means this saved source cannot yet be closed. Destination mints, token-approval cancellation, non-EVM signed sources and other mainnet networks are outside this extension. Cancelling a funding transaction does not revoke existing token approvals.


## Renew an expired NEAR Stellar source

For native XLM and supported Stellar classic assets, `next-action` can return `renew-source` after the saved source envelope expires. This covers a saved signature and an uncertain submission. A missing receipt or an expired Route review alone does not permit replacement.

Call `POST /payments/{id}/transaction/renew` with the current `authorizationId` and a persisted UUID `Idempotency-Key`, or use `sdk.renewSource(paymentId, authorizationId, requestKey)`.

Route verifies the original signature and transaction identity, a fresh closed Stellar ledger beyond the envelope’s finite time bound, and the sender’s unused sequence. NEAR must still report the same unfunded deposit within its original funding and refund deadlines. Current wallet authority, balances, receiving capacity, fees, policy and API-key restrictions must pass.

A successful response has `object: "source_renewal"`, the old and new authorization IDs, and a new `walletApproval`. Retrieve `next-action`, sign the new intent message, confirm it, then sign and submit the new transaction through the normal API. Renewal itself sends no funds. The new transaction keeps the original payment, provider deposit, memo, amount, asset, recipient and sequence; its fee and expiry are reviewed again.

Persist the renewal request key. Repeating it returns the same replacement authorization even if the payment has since advanced; always read `next-action` before continuing. Old authorization IDs cannot sign or submit the replacement. `preparation.renewalHistory` exposes the newest twenty renewal proofs and previous source hashes, without signed payloads.

Unused credit and API-limit holds stay attached to the same payment. Renewal does not count as delivery, charge another payment fee or release those holds. If the old transaction executed, its sequence is consumed, evidence is unavailable, or the provider deposit deadline passed, retain the existing payment and reconcile it. This section covers NEAR Stellar classic source payments; it does not renew Soroban source calls or an already funded transfer. Solana and Fogo use the proof described below.


## Renew an expired NEAR Solana or Fogo source

When `next-action` offers `renew-source`, call `sdk.renewSource(paymentId, authorizationId, requestKey)` with a saved UUID request key. This uses the same endpoint and response as Stellar source renewal. It supports the released NEAR SOL/original SPL-token and native FOGO source builders.

Route verifies that the old blockhash has expired at finalized commitment, checks the exact saved signature and receipt, and examines linked finalized blocks back to the original blockhash. The scan is bounded to 512 blocks within the most recent 2,048 slots. Missing, inconsistent or older history requires reconciliation and leaves the action unchanged. A missing signature in an RPC index alone is insufficient.

The original provider deposit must still be unfunded and within its funding deadlines. The replacement preserves its asset, amount, sender and destination, with a fresh blockhash and reviewed network fee. Read `next-action`, approve the new wallet intent, then sign the new transaction. Old signatures remain in history and cannot be submitted through Route after renewal. Credits and API-limit holds remain attached to the same payment.

## Reconcile a Bitcoin replacement

For NEAR or SwapKit native Bitcoin sources, submit a replacement hash only after the wallet has created and broadcast that transaction. Route verifies the outcome; this endpoint does not construct a replacement or increase its fee.

```js
const recovery = await sdk.recoverBitcoinSource(
  paymentId, authorizationId, replacementHash,
  'funding', // or 'cancellation' to explicitly close an unfunded action
  recoveryRequestKey,
);
const next = await sdk.getNextAction(paymentId);
```

`POST /payments/{id}/transaction/bitcoin-recover` requires `payments:write`, a UUID `Idempotency-Key`, and the exact same request body on retries. Route checks at least six canonical confirmations, every original input and signature, and each output. Supported replacements spend exactly the saved ordered input set. A funding replacement pays the original deposit its exact amount; any change returns to the source wallet. A cancellation returns funds only to that wallet, and the provider deposit must have no confirmed or mempool funding. Additional inputs, unrelated outputs or changed deposit amounts require separate reconciliation.

Verified funding becomes the tracked source hash on the existing payment. `transaction.sourceRecovery` preserves its link to the original hash; delivery still requires independent destination verification. Original signatures and spent-input reservations remain in history. No second payment credit hold is created.

A verified cancellation closes an unfunded standalone payment or unfunded first composed transfer, releasing unused payment credits and API-limit holds once. A later composed transfer closes only that attempt, retaining the funded parent and accounting for the cancellation fee; preview a fresh continuation on the parent. BTC pricing must be fresh to record that fee. Quote charges and network fees remain payable. Unconfirmed, missing or conflicting evidence leaves the payment and holds unchanged.

For an EVM- or Aptos-source NEAR payment reporting `MINIMUM_OUTPUT_NOT_MET` and `quoteReviewAvailable: true`, [review the same captured allocation](composed-payments.md#review-a-rejected-near-continuation) before requesting wallet authorization. Standalone payments require explicit acceptance of the revised minimum and cost ceiling. Composed continuations cannot reduce the parent’s final minimum. Acceptance does not submit funds.

### TRON settlement amount differences

`TRON_SETTLEMENT_AMOUNT_MISMATCH` means the solidified recipient credit differs from the provider report. Keep tracking the same payment; do not send another deposit. Route may reconcile Ethereum ERC-20 → TRON USDT payouts using linked source funding, NEAR withdrawal, actual bridge fees and the exact TRON recipient credit. Completion requires finalized evidence, the accepted recipient minimum and an exclusive payout claim. A larger balance alone is insufficient. Unexplained differences remain pending for investigation.

## Recover an expired Stellar protocol source

For Circle CCTP, LayerZero and Axelar Stellar sources, follow the payment's `next-action`. An expired **unsigned** burn or transfer may offer `renew-source`. Call `POST /payments/{id}/transaction/renew` with its `authorizationId` and a new UUID `Idempotency-Key`. Route verifies non-execution and the unused wallet sequence, then builds a replacement with fresh wallet, fee, policy and amount checks. Sign only the newly returned action.

An expired **signed** source cannot be renewed. When `close-payment` is offered, call `POST /payments/{id}/transaction/close` with its `authorizationId` and an idempotency key. Route requires proof that the saved transaction can no longer execute and its sequence remains unused. It closes the unfunded standalone payment and releases its sequence and unused holds; it retains all signatures and transaction evidence. Obtain a new quote to start a new payment.

An abandoned approval may also expose `transaction.closeEnabled` as an explicit alternative to renewal. Missing receipts alone never permit closure. Included transactions and composed payments are excluded; `CLOSE_UNAVAILABLE` preserves the existing payment and reservations. These operations submit no funds. The SDK exposes `renewSource`, `renewApproval` and `closePayment`. For Stellar, call `closePayment(id, authorizationId, key)`. An expired unsigned EVM action also needs the finalized transaction hash as a fourth argument; see [closing an unused EVM protocol action](#close-an-unused-evm-protocol-action).
