Idempotency
Idempotency
Every wallet mutation must be idempotent by idempotency_key.
idempotency_key is required on every mutation request.
Why
- network timeout may occur after wallet write
- OpenPoly and operator wallet are not one atomic transaction
- retries and manual reconciliation must not double-charge the user
Expectations
- same key + same user, direction, amount, currency, reason, and material metadata -> same effective result
- same key + conflicting params -> reject or return a conflict without creating a second mutation
- retain the key, request fingerprint, and effective result permanently; do not expire deduplication after a short retry/cache window or reuse a key after an operational reset
- key scope is per operator wallet system; a new HTTP request ID does not create a new financial operation
Example keys
mini_app_order:1:debitmini_app_order:1:debit_reversalmini_app_order:2:creditmini_app_redemption_item:42:creditmini_app_polyflight_purchase:{purchaseRequestId}:debitmini_app_polyflight_purchase:{purchaseRequestId}:debit_reversalmini_app_polyflight_ticket:{ticketId}:winmini_app_polyflight_ticket:{ticketId}:refundmini_app_polyflight_ticket:{ticketId}:loss
Recovery rule
Raw REST v1 mutation responses must return succeeded, failed, or pending. The Mini App processor may record an internal unknown result after a timeout, network failure, or ambiguous HTTP response; that internal state does not allow unknown in a direct mutation response.
| Direct mutation outcome | Mini App processor behavior |
|---|---|
Timeout, network error, or invalid HTTP 2xx response | Preserve internal unknown state and send the primary operation to manual review. |
HTTP 408, 429, or 5xx without a valid REST v1 status body | Preserve internal unknown state because the mutation may already have landed. |
Other HTTP 4xx without a valid status body | Treat as a failed mutation; do not assume a wallet write occurred. |
Any HTTP response with a valid succeeded, failed, or pending body | Honor the body. pending remains ambiguous and requires reconciliation. |
| Non-null identity echo conflicts with the requested user, key, currency, or amount | Preserve internal unknown with REST_V1_MUTATION_IDENTITY_MISMATCH, even if the body claims success/failure or arrived with non-2xx. |
When the operation is tied to a known operator user, direct mutation responses should include external_user_id so reconciliation tools can display the affected wallet without decoding the idempotency key.
Reconcile against authenticated operator-ledger evidence for the original user, direction, amount, currency, and key. A webhook replay repeats notification only. A dashboard wallet adjustment is a separate mutation, not resolution of the ambiguous original. Operator-facing cash-flow and Polyflight views are read-only for financial resolution; an authorized Polynion Admin decision records confirmed applied/not-applied evidence without sending another payment.
Polyflight loss credits have an exactly zero amount but still require durable request fingerprints and acknowledgement records. Duplicate delivery of the same :loss key returns the original effective result without another result record or any balance change. Ambiguity still requires authenticated wallet evidence and the existing manual-resolution process, not a new key. remote_reference is optional.
Preserve already-planned/dispatched requests exactly, including older win metadata without result. Do not add new metadata to their fingerprints or regenerate historical losing-ticket deliveries. Newly planned win credits use result: "won"; newly settled losses use operation: "loss" and result: "lost".
