diff --git a/src/hooks/useRestartOnOdometerImagesFailure/index.native.ts b/src/hooks/useRestartOnOdometerImagesFailure/index.native.ts
index 9db08faa2152..e3ebe5bd8640 100644
--- a/src/hooks/useRestartOnOdometerImagesFailure/index.native.ts
+++ b/src/hooks/useRestartOnOdometerImagesFailure/index.native.ts
@@ -1,5 +1,6 @@
-// On native blob:// URLs don't exist, so there is nothing to check
+// On native blob:// URLs don't exist, so there is nothing to check —
+// callers can always proceed with blob-dependent side-effects (like stitching)
 
-const useRestartOnOdometerImagesFailure = () => {};
+const useRestartOnOdometerImagesFailure = (): {hasVerifiedBlobs: boolean} => ({hasVerifiedBlobs: true});
 
 export default useRestartOnOdometerImagesFailure;
diff --git a/src/hooks/useRestartOnOdometerImagesFailure/index.ts b/src/hooks/useRestartOnOdometerImagesFailure/index.ts
index 68f3ed0f3c20..c10cec9238c9 100644
--- a/src/hooks/useRestartOnOdometerImagesFailure/index.ts
+++ b/src/hooks/useRestartOnOdometerImagesFailure/index.ts
@@ -1,4 +1,4 @@
-import {useEffect, useRef} from 'react';
+import {useEffect, useRef, useState} from 'react';
 import type {OnyxEntry} from 'react-native-onyx';
 import useOnyx from '@hooks/useOnyx';
 import {checkIfLocalFileIsAccessible} from '@libs/actions/IOU/Receipt';
@@ -17,9 +17,27 @@ import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
 // This is because until the request is saved, the image files are only stored in the browser's memory as blob:// URLs
 // and if the browser is refreshed, then the images cease to exist.
 // The best way for the user to recover from this is to start over from the start of the request process.
-const useRestartOnOdometerImagesFailure = (transaction: OnyxEntry<Transaction>, reportID: string, iouType: IOUType, backToReport: string | undefined, onBackupHandled?: () => void) => {
+// Returns `hasVerifiedBlobs` so callers can gate dependent side-effects (e.g. odometer image stitching)
+// until this check has confirmed the blobs are still readable. When there are no blob URLs to verify
+// (e.g. native file:// paths or remote URLs), `hasVerifiedBlobs` is `true` as soon as Onyx has loaded.
+const useRestartOnOdometerImagesFailure = (
+    transaction: OnyxEntry<Transaction>,
+    reportID: string,
+    iouType: IOUType,
+    backToReport: string | undefined,
+    onBackupHandled?: () => void,
+): {hasVerifiedBlobs: boolean} => {
     const [, draftTransactionsMetadata] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
     const hasCheckedRef = useRef(false);
+    const [asyncVerificationPassed, setAsyncVerificationPassed] = useState(false);
+
+    const hasBlobUrls = (() => {
+        if (!transaction) {
+            return false;
+        }
+        const paths = [getOdometerImageUri(transaction.comment?.odometerStartImage), getOdometerImageUri(transaction.comment?.odometerEndImage), transaction.receipt?.source?.toString()];
+        return paths.some((path) => !!path && path.startsWith('blob:'));
+    })();
 
     useEffect(() => {
         if (!transaction || isLoadingOnyxValue(draftTransactionsMetadata)) {
@@ -76,6 +94,7 @@ const useRestartOnOdometerImagesFailure = (transaction: OnyxEntry<Transaction>,
         ).then((results) => {
             const canBeRead = results.every(Boolean);
             if (canBeRead) {
+                setAsyncVerificationPassed(true);
                 return;
             }
 
@@ -84,6 +103,11 @@ const useRestartOnOdometerImagesFailure = (transaction: OnyxEntry<Transaction>,
             navigateToStartMoneyRequestStep(CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER, iouType, transaction.transactionID, reportID, CONST.IOU.ACTION.CREATE, backToReport);
         });
     }, [draftTransactionsMetadata, transaction, iouType, reportID, backToReport, onBackupHandled]);
+
+    const isOnyxLoading = isLoadingOnyxValue(draftTransactionsMetadata);
+    const hasVerifiedBlobs = !!transaction && !isOnyxLoading && (!hasBlobUrls || asyncVerificationPassed);
+
+    return {hasVerifiedBlobs};
 };
 
 export default useRestartOnOdometerImagesFailure;
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
index 3abe6758135d..3000faa18340 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx
@@ -240,7 +240,7 @@ function IOURequestStepConfirmation({
 
     const odometerStartImage = transaction?.comment?.odometerStartImage;
     const odometerEndImage = transaction?.comment?.odometerEndImage;
-    useRestartOnOdometerImagesFailure(isOdometerDistanceRequest ? transaction : undefined, reportID, iouType, backToReport);
+    const {hasVerifiedBlobs} = useRestartOnOdometerImagesFailure(isOdometerDistanceRequest ? transaction : undefined, reportID, iouType, backToReport);
 
     // Pre-insert Search is only useful for flows whose submit ends in handleNavigateAfterExpenseCreate
     // (which navigates to Search). Flows that use dismissModalAndOpenReportInInboxTab (PAY,
@@ -553,9 +553,7 @@ function IOURequestStepConfirmation({
                 odometerStartImage={odometerStartImage}
                 odometerEndImage={odometerEndImage}
                 transaction={transaction}
-                reportID={reportID}
-                backToReport={backToReport}
-                iouType={iouType}
+                hasVerifiedBlobs={hasVerifiedBlobs}
                 onStitchingChange={setIsStitchingReceipt}
                 onStitchError={setStitchError}
             />
@@ -648,7 +646,7 @@ function IOURequestStepConfirmation({
                                 isPolicyExpenseChat={isPolicyExpenseChat}
                                 policyID={policyID}
                                 isOdometerDistanceRequest={isOdometerDistanceRequest}
-                                isLoadingReceipt={isStitchingReceipt}
+                                isLoadingReceipt={isStitchingReceipt || (isOdometerDistanceRequest && !hasVerifiedBlobs)}
                                 isPerDiemRequest={isPerDiemRequest}
                                 shouldShowSmartScanFields={shouldShowSmartScanFields}
                                 action={action}
diff --git a/src/pages/iou/request/step/confirmation/OdometerReceiptStitcher.tsx b/src/pages/iou/request/step/confirmation/OdometerReceiptStitcher.tsx
index a27c431686c6..8dc28a02243c 100644
--- a/src/pages/iou/request/step/confirmation/OdometerReceiptStitcher.tsx
+++ b/src/pages/iou/request/step/confirmation/OdometerReceiptStitcher.tsx
@@ -2,15 +2,12 @@ import {useIsFocused} from '@react-navigation/native';
 import {useEffect, useRef} from 'react';
 import type {OnyxEntry} from 'react-native-onyx';
 import useLocalize from '@hooks/useLocalize';
-import clearOdometerDraftTransactionState from '@libs/actions/OdometerTransactionUtils';
-import {navigateToStartMoneyRequestStep} from '@libs/IOUUtils';
 import Log from '@libs/Log';
 import {getOdometerImageName, getOdometerImageType, getOdometerImageUri} from '@libs/OdometerImageUtils';
 import stitchOdometerImages from '@libs/stitchOdometerImages';
 import {cancelSpan, endSpan, startSpan} from '@libs/telemetry/activeSpans';
-import {checkIfLocalFileIsAccessible, setMoneyRequestReceipt} from '@userActions/IOU/Receipt';
+import {setMoneyRequestReceipt} from '@userActions/IOU/Receipt';
 import CONST from '@src/CONST';
-import type {IOUType} from '@src/CONST';
 import type {Transaction} from '@src/types/onyx';
 import type {FileObject} from '@src/types/utils/Attachment';
 
@@ -19,9 +16,7 @@ type OdometerReceiptStitcherProps = {
     odometerStartImage: FileObject | string | null | undefined;
     odometerEndImage: FileObject | string | null | undefined;
     transaction: OnyxEntry<Transaction>;
-    reportID: string;
-    backToReport: string | undefined;
-    iouType: IOUType;
+    hasVerifiedBlobs: boolean;
     onStitchingChange: (isStitching: boolean) => void;
     onStitchError: (error: string) => void;
 };
@@ -37,9 +32,7 @@ function OdometerReceiptStitcher({
     odometerStartImage,
     odometerEndImage,
     transaction,
-    reportID,
-    backToReport,
-    iouType,
+    hasVerifiedBlobs,
     onStitchingChange,
     onStitchError,
 }: OdometerReceiptStitcherProps) {
@@ -51,7 +44,10 @@ function OdometerReceiptStitcher({
     } | null>(null);
 
     useEffect(() => {
-        if (!isOdometerDistanceRequest || !isFocused || !transaction) {
+        // Wait until useRestartOnOdometerImagesFailure has confirmed the blob URLs are still
+        // readable. Stitching a dead blob after a browser refresh would race with that hook's
+        // redirect and leave the UI stuck on the E screen
+        if (!isOdometerDistanceRequest || !isFocused || !transaction || !hasVerifiedBlobs) {
             return;
         }
 
@@ -83,76 +79,42 @@ function OdometerReceiptStitcher({
         onStitchingChange(true);
         onStitchError('');
 
-        const runStitch = () => {
-            startSpan(CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH, {
-                name: CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH,
-                op: CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH,
-            });
+        startSpan(CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH, {
+            name: CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH,
+            op: CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH,
+        });
 
-            stitchOdometerImages(odometerStartImage, odometerEndImage)
-                .then((stitchedImage) => {
-                    if (ignore || !stitchedImage) {
-                        cancelSpan(CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH);
-                        return;
-                    }
-                    setMoneyRequestReceipt(transaction.transactionID, getOdometerImageUri(stitchedImage), getOdometerImageName(stitchedImage), true, getOdometerImageType(stitchedImage));
-                    lastStitchedImages.current = {startImage: odometerStartImage, endImage: odometerEndImage};
-                    endSpan(CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH);
-                })
-                .catch((error: unknown) => {
+        stitchOdometerImages(odometerStartImage, odometerEndImage)
+            .then((stitchedImage) => {
+                if (ignore || !stitchedImage) {
                     cancelSpan(CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH);
-                    if (ignore) {
-                        return;
-                    }
-                    Log.warn('stitchOdometerImages failed', {error});
-                    onStitchError(translate('iou.error.stitchOdometerImagesFailed'));
-                })
-                .finally(() => {
-                    if (ignore) {
-                        return;
-                    }
-                    onStitchingChange(false);
-                });
-        };
-
-        // Pre-flight: verify blob URLs haven't expired before attempting to stitch.
-        const localImages = [
-            {uri: startUri, image: odometerStartImage},
-            {uri: endUri, image: odometerEndImage},
-        ].filter((item): item is {uri: string; image: typeof odometerStartImage} => !!item.uri && item.uri.startsWith('blob:'));
-
-        let hasExpiredImages = false;
-        Promise.all(
-            localImages.map(({uri, image}) =>
-                checkIfLocalFileIsAccessible(
-                    getOdometerImageName(image),
-                    uri,
-                    typeof image === 'object' ? image?.type : undefined,
-                    () => {},
-                    () => {
-                        hasExpiredImages = true;
-                    },
-                ),
-            ),
-        ).then(() => {
-            if (ignore) {
-                return;
-            }
-            if (hasExpiredImages) {
+                    return;
+                }
+                setMoneyRequestReceipt(transaction.transactionID, getOdometerImageUri(stitchedImage), getOdometerImageName(stitchedImage), true, getOdometerImageType(stitchedImage));
+                lastStitchedImages.current = {startImage: odometerStartImage, endImage: odometerEndImage};
+                endSpan(CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH);
+            })
+            .catch((error: unknown) => {
+                cancelSpan(CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH);
+                if (ignore) {
+                    return;
+                }
+                Log.warn('stitchOdometerImages failed', {error});
+                onStitchError(translate('iou.error.stitchOdometerImagesFailed'));
+            })
+            .finally(() => {
+                if (ignore) {
+                    return;
+                }
                 onStitchingChange(false);
-                clearOdometerDraftTransactionState(transaction);
-                navigateToStartMoneyRequestStep(CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER, iouType, transaction.transactionID, reportID, CONST.IOU.ACTION.CREATE, backToReport);
-                return;
-            }
-            runStitch();
-        });
+            });
 
         return () => {
             ignore = true;
             onStitchingChange(false);
             cancelSpan(CONST.TELEMETRY.SPAN_ODOMETER_IMAGE_STITCH);
         };
-    }, [isOdometerDistanceRequest, isFocused, odometerStartImage, odometerEndImage, transaction, reportID, backToReport, translate, iouType, onStitchingChange, onStitchError]);
+    }, [isOdometerDistanceRequest, isFocused, odometerStartImage, odometerEndImage, transaction, hasVerifiedBlobs, translate, onStitchingChange, onStitchError]);
 
     return null;
 }
