A deep link should have exactly one door.
If a cold start and a running app go through different handlers, you are keeping two sets of checks in sync, and they drift. One of them will eventually accept something the other refuses, and the bug report will say "only happens when the app was already open".
In our open-source project deeplinkmap, the intake class is that single door. Both paths end in the same method.
The cold path takes whatever the platform's initial-URL lookup resolved. If that value is null or undefined, intake returns null, because no link is not a delivery and it must not be reported as one. Anything else is forwarded with the source marked cold. The warm path calls the same method and the source defaults to warm. Same checks, same order, one difference: a label.
The order inside is fixed, and the order is the design.
Read the clock once. Expire remembered links first: anything whose elapsed time exceeds the dedupe window is forgotten, and so is anything with a negative elapsed time, because a clock that moved backwards must not pin a link in memory forever. Only then consult what has been seen.
If the link is still remembered, it comes back as a delivery with status duplicate, carrying both the url and the source, and it goes through the audit hook before it is dropped. That part matters to us more than the dedupe itself. A dropped repeat is recorded exactly like an acceptance or a refusal. Silent deduplication is indistinguishable from a broken router at 2am.
If it is new, the link is written down at the current time. The window is anchored to the first sighting, not to the latest repeat, so a stream of repeats cannot hold the door shut indefinitely. Then the router resolves it: matched with a match, or refused with a refusal.
Two deliberate refusals to be clever: links are compared as received, so two different URLs stay two links and no normalization quietly merges them. And the window is validated at construction, where not finite or below one millisecond throws immediately rather than degrading into dedupe that never triggers.
The default window is three seconds. Long enough to absorb the double-fire you get when the OS hands the same link to a resuming app, short enough that a user tapping the same notification again on purpose still gets through.
If you dedupe deep links, the anchor you picked is worth a second look (first sighting, or the latest repeat), and so is whether your logs can prove that a specific drop actually happened.