---
name: feedback_magic_link_scanner_prefetch
description: "Magic-Link/OTP-Auth bricht in Enterprise-Mail — nie GET-verify-Link, immer /auth/confirm mit token_hash-on-click"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: a70158c8-cc8e-4c4d-8d60-a1b83aa3c690
---

Bei jedem Magic-Link- / OTP- / Recovery-Link-Auth-Flow (Supabase, aber gilt allgemein): den E-Mail-Link **niemals** direkt auf den GET-`/verify`-Endpoint zeigen lassen, der den Einmal-Token beim ersten Abruf einlöst. Enterprise-Mailsecurity (Microsoft Defender/SafeLinks, Proofpoint) UND Chat-Link-Vorschauen (Telegram, Slack) **prefetchen** Links und verbrauchen den Token, bevor der echte Empfänger klickt → User sieht „otp_expired / link invalid".

**Why:** 2026-06-21 im Kadi-v2/BMW-Prod-Test reproduziert: Invite-Link landete auf Supabase `/auth/v1/verify`, jeder Klick gab `otp_expired` — sowohl über Telegram-Vorschau als auch auf dem BMW-Rechner. Der Token war jedes Mal vorab verbraucht.

**How to apply:** Eigene `/auth/confirm`-Seite bauen, die den `token_hash` aus der URL nimmt und `verifyOtp({ type, token_hash })` erst bei **explizitem User-Klick** (Button) ausführt. Link via `generateLink().properties.hashed_token` selbst bauen, nicht `action_link` nutzen. Ein Prefetch rendert dann nur die Seite und verbraucht nichts. Zusätzlich: Supabase Site URL + Redirect-Allowlist müssen auf die Prod-Domain zeigen (sonst fällt `redirect_to` auf localhost zurück). Siehe Kadi-v2 KAR-772, [[reference_supabase_legacy_keys_cutover]].
