# Recover a refund without issuing it twice Date: October 29, 2026 Author: Thomas Neckel Position: CEO and Co-founder Source: https://fragment.dev/blog/lost-refund-response [Documentation index](https://fragment.dev/llms.txt) A refund request can succeed while the application making it sees no response. The customer may already have a refund in progress. The application only knows that it did not receive an acknowledgement. Treating those facts as equivalent can turn one approved refund into two financial actions. Recovery therefore starts with the identity of the original intent. ## Record the intent before the call An approved refund needs a durable identity, an original payment, an amount and an approval record. The application stores those facts before submission. Retries use that identity and the original request parameters. Provider idempotency and business uniqueness solve different problems. An idempotency key identifies a repeated request. An application constraint prevents two different keys from representing the same approved action. Creating a new key after a timeout can defeat the first protection without satisfying the second. Provider contracts also have boundaries. For example, [Stripe API v1 can prune idempotency keys after at least 24 hours](https://docs.stripe.com/api/idempotent_requests). Reusing a pruned key can create a new request. An application's durable intent record needs to survive its provider's replay window. And once a response is missing, the recorded outcome should stay unknown until supported evidence resolves it. A timeout describes what the caller observed. It does not establish what happened to the money. ## Recover the original refund In a Fragment development-sandbox test, we submitted one $100 refund against an existing prepaid payment. The request succeeded, but the test deliberately withheld its successful reply from the simulated application caller. Test instrumentation retained the reply so the outcome could be checked independently of what the caller saw. The caller then recovered the original action: 1. Query the canonical payment using its original payment key and ledger. 2. Find the refund using its original refund key. 3. Replay the exact same refund request with that same key. 4. Inspect the returned identity and the ledger effects. The replay returned the original refund with `isIkReplay: true`. There was still one refund and no duplicate refund entry. The refund later settled. That result demonstrates recovery from a successful reply withheld at the caller. It does not demonstrate loss of an upstream processor acknowledgement, a request interrupted before acceptance, concurrent retries or indefinite key retention. Those cases need their own supported contracts and tests. ## Repair the missing observation The application still needs a resolver for unknown outcomes. It should retrieve the original payment or refund through a supported lookup, update its observation and preserve the link to the approved intent. If the lookup cannot establish the result, keep the outcome unresolved and provide an operator path. The same distinction applies to accounting. A missing application observation does not justify a second journal effect for a native movement already recorded. [Ledger-write idempotency](https://fragment.dev/guides/post-ledger-entries.md) protects a repeated posting identity; it is not a substitute for establishing the external payment outcome. From submission to settlement, the application may learn the result late. It should recover the original refund. ## Evidence appendix This check used the same sandbox lifecycle as the [prepaid example](https://fragment.dev/blog/prepaid-payment-lifecycle.md). The test withheld a successful reply at the caller; instrumentation retained it for comparison. Original payment, refund and ledger identities are omitted. | Check after canonical lookup and exact same-key replay | Observed result | | --- | --- | | Replay marker | `isIkReplay: true` | | Returned refund identity | Equal to the original refund identity retained by instrumentation. | | Refund count | `1` | | Total ledger entries | `4`, unchanged by replay; no additional refund entry. | | Eventual refund status | `settled` | These are selected observations, not a complete API response or a test of every failure point. The [native payment guide](https://fragment.dev/guides/make-payments.md) covers Payment Type accounting, and [posting ledger entries](https://fragment.dev/guides/post-ledger-entries.md) covers ledger-write keys. The payment request's replay contract and ledger-write replay remain distinct.