Safari ITP and SaaS attribution: what the 7-day cap deletes
Safari ITP caps JavaScript cookies at 7 days, or 1 day after some ad clicks, so SaaS payments land in Direct. What it deletes and how to keep the source.
Muzahid Maruf, FounderUpdated
On this page
Explore with AI
Opens this article inside the chosen assistant with a ready-made prompt.
Safari ITP attribution loss comes from the way Intelligent Tracking Prevention (ITP) shortens anything your site writes to the browser with JavaScript.
Since February 2019, a cookie created that way expires after 7 days, and after 1 day when a classified tracker domain sent the visitor to a URL with a query string.
A 14-day trial can reach Stripe with no source, and the payment is filed under Direct. A source stored in your own database and on the Stripe payment survives.
Key takeaways
- ITP 2.1 capped JavaScript-written cookies at 7 days in February 2019, and ITP 2.2 cut that to 1 day after a tagged link from a classified domain.
- Since March 2020 Safari also deletes LocalStorage, IndexedDB and other script-writable storage after 7 days of use without interaction.
- Cookies your server sets in a response have no ITP cap, except third-party CNAME-cloaked ones, which get 7 days.
- A day-0 signup keeps its source while the day-14 payment loses it and lands in Direct.
- Log the click at a redirect you own, bind the email at signup and store the visitor ID in Stripe metadata.
What Safari ITP caps and deletes
Apple shipped ITP in 2017 and has added rules since.
| Rule | Limits | Trigger | Announced |
|---|---|---|---|
| 7-day cookie cap | document.cookie cookies | Any persistent cookie created with JavaScript | ITP 2.1, Safari 12.1, iOS 12.2, Feb 21, 2019 |
| 1-day cookie cap | document.cookie cookies set on the landing page | Classified sender plus a query string or fragment | ITP 2.2, iOS 12.3, Apr 24, 2019 |
| 7-day storage deletion | LocalStorage, IndexedDB, SessionStorage, media keys, service workers | 7 days of Safari use without interaction | ITP 2.3 on tagged navigations, Sep 23, 2019; Safari 13.1 on every site, iOS 13.4, Mar 24, 2020 |
| Third-party cookie block | Cross-site cookies | Always, unless the Storage Access API grants access | Safari 13.1, iOS 13.4, Mar 24, 2020 |
| CNAME cloaking cap | Cookies set in HTTP responses, capped at 7 days | A subresource resolves through a third-party CNAME | Safari 14, iOS 14, Nov 12, 2020 |
Dates are the WebKit blog post dates.
A machine learning model picks the classified domains, and WebKit's tracking prevention page publishes no list, so you cannot check a campaign in advance.
Tagged links always meet the query-string condition, which leaves the sender to decide whether the 1-day cap applies.
Cookies set in an HTTP response
WebKit limited the 2019 caps to cookies created through document.cookie, so a cookie your server sets in a response keeps its expiry, with or without the HttpOnly flag.
The exception is CNAME cloaking: cookies set in responses from a subdomain such as track.blog.example that resolves to tracker.example get 7 days.
Apple's definition covers subresources, and I found nothing from Apple on a top-level redirect through a vendor domain, so read the cookie's expiry in Safari if your tracking subdomain points at a vendor.
Safari ITP attribution loss in a 14-day trial
A visitor clicks a tagged newsletter link in Safari, starts a trial in the same session and pays on day 14. The day-0 signup carries its source.
What the payment finds depends on the gap since the last page view that ran your pixel.
| Last page view with your pixel | Cookie expires after a normal view (7-day cap) | Cookie expires after a tagged landing from a classified domain (1-day cap) |
|---|---|---|
| Day 0 | Day 7 | Day 1 |
| Day 5 | Day 12 | Day 6 |
| Day 8 | Day 15 | Day 9 |
| Day 12 | Day 19 | Day 13 |
| Day 14 | Day 21 | Day 15 |
The payment lands on day 14, so a cookie that expires on day 14 or earlier is already gone. Assumes the pixel rewrites its cookie on every page view.
A prospect who ignores the onboarding emails and pays on the day-14 reminder is the first row: the cookie is gone, the charge lands in Direct and the click row at your redirect is untouched.
Paid social is the likeliest to meet both conditions of the 1-day cap, since a social app is a plausible classified sender and ad URLs carry parameters.
For scale, Statcounter's September 2026 data puts Safari at 18.27% of worldwide browser usage and 46.38% of US mobile usage. Your customer mix sets the real figure.
Take 80 new customers every month at $79 each, 40% of them on Safari, with Direct absorbing every lost source.
| Safari customers losing their source | Filed as Direct | MRR | 12-month revenue |
|---|---|---|---|
| 25% | 8 | $632 | $7,584 |
| 50% | 16 | $1,264 | $15,168 |
| 75% | 24 | $1,896 | $22,752 |
Invented numbers: 80 new customers per month, 40% on Safari (32), each paying $79 monthly for 12 months.
Which attribution setups ITP breaks
| Where the source lives | Expiry in Safari |
|---|---|
| Cookie your pixel writes with JavaScript | 7 days, or 1 day after a tagged landing from a classified domain |
| UTM tags in LocalStorage or SessionStorage | Deleted after 7 days of Safari use without interaction |
| Cookie your server sets on your own domain | No cap since ITP 2.1 |
| Cookie from a subdomain that CNAMEs to a vendor | 7 days when it counts as CNAME cloaking |
| Third-party cookie on a vendor domain | Blocked since Safari 13.1 |
| Click row or visitor ID in your database or Stripe metadata | Outside the browser; Stripe takes 50 keys of 500 characters |
Link Tracking Protection, which removes known tracking parameters from links opened in Messages, Apple Mail and Private Browsing, is a separate Apple feature, covered in iOS 17 Link Tracking Protection and what survives.
1. Log the click at a redirect you own
Send shared links through a short link such as go.example.com/spring-launch.
The redirect stores the click (visitor ID, link, timestamp, user agent, referrer) and sets the visitor cookie in its 302 response, with Domain set to your registrable domain, outside the script caps.
The server-side versus client-side comparison covers what that log sees.
2. Bind the email to the visitor on day 0
Read the visitor ID from the cookie your redirect set and save it in the same request that creates the user, the one moment the ID and the email arrive together.
A script-written cookie may not last from the click to a signup 8 days later, and an ID that never reaches the user record leaves that click unlinked.
3. Write the visitor ID onto the payment
Stripe Checkout takes a client_reference_id of up to 200 characters, and metadata allows 50 pairs with keys up to 40 characters and values up to 500. Metadata on the session stays on the session. subscription_data.metadata saves to the Subscription.
The Stripe metadata attribution setup post has a field mapping.
// vid comes from the cookie your server set at the redirectconst click = await db.clicks.findFirstByVisitor(vid); const session = await stripe.checkout.sessions.create({ mode: "subscription", line_items: [{ price: "price_123", quantity: 1 }], client_reference_id: vid, subscription_data: { metadata: { vid, utm_source: click?.utmSource ?? "", utm_medium: click?.utmMedium ?? "", utm_campaign: click?.utmCampaign ?? "", }, }, success_url: "https://app.example.com/welcome", cancel_url: "https://www.example.com/pricing",});4. Set the attribution window past the trial
Windows count back from the payment.
A trial of 14 days under a window of 30 days means a click counts only if it came within 16 days of the trial start, so a pricing-page visit 21 days earlier drops out.
Cover the trial plus your usual research time.
5. Test in Safari before you trust the numbers
The ITP 2.3 post describes ITP Debug Mode (Develop menu, Safari 13 on macOS) and a defaults command that classifies a test domain as a tracker. WebKit wrote those steps in 2019, so confirm them on your version.
WebKit says ITP Debug Mode was available in Safari Technology Preview well before regular Safari 13 got it.
defaults write com.apple.Safari ITPManualPrevalentResource sender.exampleClick from that domain to a URL with a query string, write a cookie with JavaScript, and read its expiry in Web Inspector.
Then split the last 30 days of paying customers into Safari and other browsers and compare how many in each group have a source.
What TrackRev does with Safari traffic
TrackRev is SaaS affiliate software with link tracking and revenue attribution built in, so partner and campaign links share one click log.
A TrackRev link logs each click server-side and sets a vid cookie in the HTTP response, good for 365 days.
The destination URL carries the ID as _vid, and your pixel copies it into a JavaScript cookie that Safari caps at 7 days, or 1 day after a tagged landing. The pixel rewrites that cookie on every page view.
trk.identify(email) attaches an email to a known visitor.
The Stripe sync resolves a visitor from a vid in charge metadata, from the Checkout Session or from the customer's email, then credits clicks inside your attribution window (1 to 365 days, set per workspace).
Renewals copy the first order's credit.
A Safari visitor who goes quiet for more than 7 days and returns through a plain URL gets a new pixel visitor with no click behind it, so the email binds to nothing.
A signup within the same session, or a return through any tracked link, keeps the chain.
The Free plan covers 50 links and 1,000 tracked events monthly, with revenue figures hidden. Revenue by channel needs Indie at $29 a month (pricing, revenue attribution).
Skip a tool if you already write UTM tags and a click ID to Stripe metadata at checkout, as in step 3, and look at a standalone attribution product if you have no partner program to run.
Google Analytics 4 showing no revenue by channel is a separate fix, covered in GA4 not showing revenue by channel.
Found this useful? Share it.
Frequently asked questions
- No. Safari has blocked third-party cookies by default since March 2020, and the Storage Access API is the only way around that. A cookie on your own domain is first party. ITP shortens the ones created with JavaScript and the CNAME-cloaked ones.
- Not for cookies created with JavaScript. A script that requests 365 days gets 7 in Safari, and only a request for under 7 days is honored. Move the write to your server.
- No. The March 2020 rule exists because trackers moved to LocalStorage after the 2019 cookie cap, and LocalStorage has no expiry function. Safari now deletes it after 7 days of use without interaction.
- Only for visitors who go quiet. If your pixel rewrites its cookie, a user who opens a page with your script every 6 days keeps the cookie for the whole trial, whatever its length.
- Apple's table shows no cap when a subdomain resolves to another subdomain of your own site, and 7 days when it resolves to a third party. A vendor-hosted redirect domain is untested here, so read the cookie's expiry after a click.
- A click logged at the redirect does not depend on browser storage, so the storage caps leave the count alone. Click IDs are a separate matter, handled by Link Tracking Protection in iOS 17 and later.
- WebKit exempts the first-party domain of a web app added to the Home Screen from its website data removal and keeps its data separate from Safari's, so a classification made in Safari does not carry over.

Written by
Founder, TrackRev.io & Contant.io
Muzahid Maruf founded TrackRev.io, SaaS affiliate software with no limit on tracked revenue, and Contant.io. He writes about affiliate programs.
Writes about Marketing attribution · Link tracking · Revenue analytics · SaaS growth
Stop guessing where your revenue comes from.
Set up TrackRev in about five minutes. The free plan covers 1,000 events a month, no card needed.
Start free