Your indexer credited a deposit. Seconds later its block was replaced by a different block at the same height, and the deposit isn't in it. Your database now describes a chain that no longer exists. That's a reorg, and any backend that reads from the chain tip has to plan for it.
Why reorgs happen
Near the tip of the chain, nodes can briefly disagree about which block is canonical: two valid blocks at the same height, network latency, a validator proposing late. Fork choice resolves it, and nodes on the losing branch switch over. Blocks on the losing branch are dropped, and transactions in them either reappear in a later block (possibly in a different order or with a different outcome) or disappear entirely, for example if a conflicting transaction with the same nonce landed first.
How deep reorgs go depends on the chain's consensus. On post-merge Ethereum, shallow reorgs of a block or so happen occasionally, and blocks become finalized after a couple of epochs. Polygon PoS historically saw deeper reorgs than Ethereum mainnet, and its finality mechanism has changed over time. The robust approach is the same everywhere: don't hard-code a depth, and don't assume a block is permanent until the chain says it is.
Use the chain's own finality signals
Most EVM clients support block tags beyond latest:
| Tag | Meaning |
|---|---|
latest | The current head. Can be reorged. |
safe | Unlikely to be reorged under normal conditions. |
finalized | Can't be reverted without the chain's finality guarantees breaking. |
Support for safe and finalized varies by chain, client and RPC provider. Call them against your provider and confirm they return what you expect before you depend on them.
The simplest correct design is: only take irreversible actions on finalized data. Show a deposit as "pending" as soon as you see it at latest, but credit balances, release goods or trigger withdrawals only once its block is at or below finalized.