Save the signature, signed bytes and validity boundary before sending. A timeout leaves delivery uncertain, so check the original transaction while using a bounded rebroadcast policy. Rebuilding with a new blockhash creates a different transaction and needs protection against repeating an action that already succeeded.
Separate the transaction from the business action
A customer wants to pay invoice INV-104. The invoice is the business action. The signed transaction is one encoded attempt to carry out that action onchain.
Rebroadcasting identical signed bytes preserves the same transaction identity. Changing the blockhash and signing again changes the message and signature, producing a separate transaction which can repeat the transfer.
Transport deduplication and business idempotency solve different problems. Even a well-behaved transaction sender needs an invoice or operation record which knows whether the intended purchase has already been fulfilled.
Persist the operation ID, original signature, signed bytes or their safely retained delivery record, cluster, construction context and validity information. Saving only the send response loses the identifier if that response never arrives.
A lost response does not establish rejection
Suppose your backend sends the transaction and then the HTTP connection times out. The service may have received and forwarded it before the connection failed. Immediately creating another payment can therefore pay twice.
Use the original signature to query status. A known successful execution with the required finality resolves the chain attempt; then reconcile the operation's accepted state.
A known execution error means the intended application changes did not commit under ordinary transaction rollback rules. Fees and special protocol bookkeeping can still apply. Diagnose the error before deciding whether a corrected attempt is appropriate.
An absent status is still uncertain within the endpoint's cache, commitment and history coverage. Check those conditions instead of converting null into failed.
Retry delivery within a bounded validity policy
For an eligible recent-blockhash transaction, the sender can rebroadcast the same signed bytes while tracking its signature and validity. Choose bounded attempts and intervals appropriate to the service rather than flooding endpoints.
Keep the preflight commitment coherent with construction. A lagging endpoint can be unaware of a newly obtained blockhash. Changing providers should preserve cluster and intent, and should account for the second provider's state.
Stop unnecessary delivery once the required outcome is known. If you receive an execution error, repeating identical bytes does not repair an invalid amount or authority.
The RPC service can also perform retries under its documented configuration. Coordinate that policy with the client so nested retry loops do not multiply traffic unpredictably.
Rebroadcasting and rebuilding differ
Use the actual validity boundary and historical evidence
A recent-blockhash transaction carries a validity boundary obtained with its blockhash. Compare the appropriate observed block height with lastValidBlockHeight under a coherent commitment policy. Slot numbers and a guessed wall-clock timeout are different quantities.
Once the boundary is passed, that transaction can no longer newly execute through its ordinary recent-blockhash validity. It may already have executed before expiry. Expiry alone does not prove that it never paid.
For a chosen example, the boundary is height 10,150. The first transaction executes at 10,121, but your response is lost. Observing 10,151 later does not undo that payment or make a second payment safe.
Reconcile the original signature and business state with adequate historical coverage before creating a replacement. Durable nonce validity follows nonce state rather than this recent-blockhash boundary.
Protect the operation when constructing a new attempt
For a plain token transfer, putting an invoice reference in the message helps identify the intended invoice. It does not make the token program reject a second transfer carrying the same reference.
A backend can track attempts and allow fulfilment only once. That prevents duplicate delivery of goods but does not alone prevent a customer from sending two onchain payments. The reconciliation process needs an overpayment policy too.
A custom program can enforce an operation ID by checking persistent state and recording consumption atomically with its action. Its authorization, concurrency and account design must enforce that rule, not just include an ID in a log.
Use the supported idempotency mechanism for any external payout API as well. An API request key, an invoice reference and an onchain consumed-operation account are distinct controls.
Implement an explicit operation state machine
A useful sender distinguishes prepared, delivery uncertain, observed success, observed execution error, expired unresolved and reconciled. Keep fulfilment status as a related business record.
Persist transitions before irreversible side effects. A worker restart should resume the same operation and signature rather than infer a new payment from an empty local cache.
Test response loss after successful delivery, repeated identical bytes, a status-cache miss, expiry after earlier success and a crash after settlement but before fulfilment. Each case should produce the intended payment and fulfilment count.
The payment idempotency guide develops the invoice and fulfilment design. Blockhash expiry explains validity in more detail.
Common questions
Should I sign again after every timeout?
No. First resolve the original signature and operation state. A timeout can follow successful delivery.
Does a new blockhash preserve transaction identity?
No. It changes the message that is signed, producing a separate transaction.
Does an invoice reference prevent a second transfer?
Not by itself. Correlation, backend fulfilment uniqueness and onchain duplicate-action enforcement are separate mechanisms.
Sources and documentation
- sendTransaction
- getSignatureStatuses
- getTransaction
- getLatestBlockhash
- Durable nonce execution and failure handling
- Solana transactions
Found an error or an outdated detail? Send a correction.
