First-Party Snippet Domain
Load apex.js from one of your own subdomains (e.g. m.skillshotgolf.com) so iOS Safari treats it as first-party. The privacy warning bar goes away, visitor cookies persist for the full 365 days instead of being capped at 7, and your data quality from Safari traffic improves.
Why It Matters
When the snippet is loaded from app.apex.inc on a customer's site, iOS Safari sees a cross-origin script doing tracker-shaped things: setting cookies, posting events to a different domain, reading screen dimensions and language. Safari's Advanced Tracking and Fingerprinting Protection (ATFP) classifies it as third-party tracking and applies these protections:
- Cookie cap at 7 days — visitor identity rolls forward less reliably; the same person looks like a new visitor every week.
- Fingerprinting API scrambling — some reads return spoofed values.
- Privacy warning banner — on pages where ATFP detects post-paint content shifts, Safari surfaces a "you can reduce advanced privacy protections" bar to the user.
CNAME loading converts the script and its API calls from cross-origin to same eTLD+1 (the same registrable domain as your site). Safari treats the whole flow as first-party — no ATFP, no cookie capping, no warning.
This is the same pattern Google Analytics 4 (Server-Side Tagging on a custom domain), HubSpot (Custom Tracking Domain), Optimizely Web (Custom Snippet), and VWO (Smartcode on First-Party Domain) all use.
How It Works
Browser merchant.com Apex
─────────────────────────────────────────────────────────────────
visit page ──> skillshotgolf.com
HTML loads html includes:
<script src="https://m.skillshotgolf.com/api/apex-js?...">
script load ──> DNS lookup of m.skillshotgolf.com
──CNAME──> Apex CloudFront distribution
──> serves apex.js
(apiBaseUrl baked in: m.skillshotgolf.com)
events POST ──> POST m.skillshotgolf.com/api/events
──> Apex CloudFront ──> Apex API
Three pieces of infrastructure get provisioned on your behalf when you submit a domain:
- An ACM certificate for your subdomain (us-east-1, issued via DNS-01 validation). You add the validation CNAMEs we provide.
- A dedicated CloudFront distribution with your subdomain as an alternate domain name. Origin is
app.apex.inc; the distribution forwardsHost: m.yourdomain.comto our API so the snippet'sapiBaseUrlmatches. - A reverse-index entry mapping your subdomain → workspace, so requests arriving at the CDN route to your project.
You only see the DNS records — everything else happens automatically.
Setup Walkthrough
Pick a subdomain
In your Apex dashboard, open Integrations and find the First-party snippet domain card. Type the subdomain you want to use — m.yourdomain.com, metrics.yourdomain.com, or whatever you prefer. It must be a subdomain (not a bare apex domain), and it must be on a public TLD that your DNS provider can serve records for.
Wait ~10 seconds
We validate the format, claim the subdomain (one Apex workspace per domain), and request the TLS certificate from ACM. The dashboard returns two DNS records you need to add at your registrar.
Add the DNS records
At your DNS provider:
- One CNAME pointing
m.yourdomain.comatdxxxxx.cloudfront.net(the CDN target shown in the dashboard). - One CNAME for ACM validation:
_xyz._acme.m.yourdomain.com→_abc.acm-validations.aws..
Most DNS providers propagate within 5-15 minutes. If you don't see status change after an hour, the dashboard surfaces a soft warning; after 24 hours it escalates so you know something's off.
Wait for `active`
The provisioning cron checks every 5 minutes and walks your domain through the lifecycle states. You can also click Check now in the card to nudge it manually. When the status flips to Active, you'll get a confirmation email and the install snippet on the same page automatically updates to use the new domain.
Update your install
Replace your existing install snippet on your site with the new copy from the dashboard. The new snippet src points at your custom domain; the inline anti-flicker stub stays the same. From this point on, iOS Safari treats Apex as first-party.
Lifecycle States
| State | What it means |
|---|---|
| Unconfigured | No domain submitted yet. |
| DNS pending | Cert requested. We're waiting for you to add the DNS records and for ACM to validate them. |
| Issuing certificate | DNS records validated, ACM is finalising the cert and we're spinning up the CloudFront distribution. Typically takes 15-30 minutes. |
| Active | Cert issued, distribution deployed, DNS resolves correctly. The snippet now loads first-party. |
| Failed | Something went wrong (cert validation timed out, DNS misconfigured, etc.). The dashboard shows a specific error message so you can fix it. Remove and re-add to retry. |
| Renew pending | Cert is within 30 days of expiry. ACM should auto-renew as long as the validation CNAMEs remain in DNS. The dashboard surfaces a banner reminding you to leave them in place. |
| Expired | Cert has expired. Restore the validation CNAMEs and contact support if your dashboard doesn't recover within an hour. |
Operational Details
Certificate auto-renewal
ACM auto-renews issued certificates around 60 days before expiry — but only if the DNS-01 validation CNAMEs are still in your DNS. Don't remove the _acme-challenge records after issue. They look optional but they're required for renewal. The dashboard surfaces a clear "renew_pending" banner once we're inside 30 days; if anything looks off, that's your cue to verify the records are still there.
Workspace transfer
If you set up a domain on a sandbox workspace and want to move it to your production workspace (or reorganise internally), use Transfer instead of disable + re-add. Transfer atomically re-points the cert + distribution to the destination workspace — no downtime, no DNS changes, no need to retype anything. You must be a workspace admin on both the source and destination.
Removal
The Disable button on the card releases the domain from Apex's side, clears the install snippet, and queues the cert + CloudFront distribution for teardown in the background. CloudFront takes ~15-30 minutes to fully delete a distribution; that's fine — your snippet has already reverted to the standard app.apex.inc URL by the time you see the confirmation.
Security
Two boundaries protect your traffic:
- Tenant isolation — the snippet endpoint cross-checks the incoming domain against an authoritative claim table. A poisoned
Hostheader from a misconfigured proxy can't make us serve a snippet that beacons events to an attacker-controlled origin. - Atomic claim — one domain belongs to exactly one Apex workspace. If you submit a domain that's already in use, you get a clear error rather than overwriting another workspace's setup.
Browser Coverage Matrix
Setting realistic expectations: first-party CNAME is the canonical industry fix for Safari specifically. It also helps in some other browsers, but for the most aggressive blockers the right answer is server-side events (next section). Use this table to know which fix to reach for.
| Browser / mode | What happens without first-party | First-party CNAME fix? | If not, what works? |
|---|---|---|---|
| Safari Private — iOS / iPadOS / macOS | Silent drops. sendBeacon returns true, request never leaves device. Visitor invisible in Live Activity. | ✅ Fully recovered | — |
| Safari regular — iOS / iPadOS / macOS | Events flow, but ITP caps cookies at 7 days. Same person looks like a new visitor every week. | ✅ Cookies extend to 365 days | — |
| Firefox Strict mode (ETP) | List-based blocking; cookies partitioned by site. | Partial — helps with the cookie cap, not always with list-based blocks | Server-side events |
| Brave (default Shields) | Aggressive behavioural + list-based blocking. | Partial — Brave can recognise CDN signatures even on first-party | Server-side events |
| Edge Strict tracking prevention | List-based blocking similar to Firefox Strict. | Partial | Server-side events |
| DuckDuckGo browser (mobile) | Aggressive list-based blocking. | Partial | Server-side events |
| iOS Lockdown Mode | Maximum protection — even more aggressive than Private mode. | Partial | Server-side events |
| Chrome (any mode), Edge Balanced, Firefox Standard | Permissive — events flow normally today. | N/A — not needed | — |
The fix that universally works regardless of browser is firing the conversion event server-to-server from your backend the moment it happens. The browser snippet measures everything it can; the server fills the gaps the browser drops. Same event.id on both paths and Apex deduplicates.
Browser-blocking-resistant Measurement
When the conversion that matters most can't be allowed to depend on the browser cooperating — payment completed, account upgraded, demo booked — fire it from your backend in addition to the snippet. The Apex Server Events API takes the same event shapes the snippet does, ingests with API-key auth, and deduplicates against the snippet's beacon if both reach the server.
curl -X POST https://app.apex.inc/api/v1/events \
-H "x-api-key: apex_sk_..." \
-H "idempotency-key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{
"events": [{
"type": "purchase_completed",
"visitorId": "vis_abc123",
"data": { "value": 99, "currency": "USD" }
}]
}'
This is the same Conversions API pattern Meta CAPI, Google Measurement Protocol, and TikTok Events API use, and it sidesteps every browser-side blocker — Brave, Firefox Strict, Edge Strict, DuckDuckGo, Lockdown Mode, future browser changes — because the request never originates from the browser.
A typical setup pairs the two:
- Snippet measures pageviews, engagement, page impact, attribution
- Server-side fires the high-stakes conversions (payment success in Stripe webhook, account-upgraded in your billing logic, etc.)
Both reach the same workspace, the same visitorId, the same dashboard.
Compatibility Notes
Cookie domain mode
When you activate a first-party snippet domain, the dashboard's install snippet automatically appends &domain=registrable. This tells the snippet to set cookies with Domain=.yourdomain.com so visitor identity follows users across all subdomains of your site (app.example.com, www.example.com, m.example.com all see the same apex_vid).
If you'd rather keep cookies host-scoped, remove the &domain=registrable parameter from your install. Most merchants want cross-subdomain cookies, which is why we suggest the registrable mode by default once you've gone through the trouble of setting up a CNAME.
Existing installs
Switching from app.apex.inc loading to a first-party domain is a soft migration: existing visitor IDs in apex_vid cookies persist (they're set on your site's domain, not the script's source domain). Visitors don't need to re-identify, attribution doesn't reset, and experiments keep running.
Mobile and SDK
This feature is web-snippet only. The mobile Capacitor plugin and SDK don't share Safari's classification — they post events directly to app.apex.inc regardless. iOS Safari ATFP doesn't apply to native networking calls.
Rate Limits and Quotas
- 5 domain submissions per hour per workspace. Generous enough for typo fixes, tight enough that an automation loop can't burn the shared ACM certificate quota.
- 20 submissions per hour per IP. Caps a misbehaving network independently of any one workspace.
- CloudFront account limit: 200 distributions. We log warnings at 75% utilisation and pause new distribution creation at 90% (existing domains keep working). Once we request and receive a quota raise, paused projects automatically resume on the next 5-minute sweep — no merchant action required.
API Reference
POST /api/snippet-domain— submit a domain. Body:{ "domain": "m.yourdomain.com" }. OptionalIdempotency-Keyheader. Returns DNS records and lifecycle status.GET /api/snippet-domain— current status, DNS records, certificate expiry.POST /api/snippet-domain/verify— manual recheck (the cron also runs every 5 minutes).POST /api/snippet-domain/transfer— atomic handoff between workspaces. Body:{ "toProjectKey": "..." }.DELETE /api/snippet-domain— disable.
All five require workspace-admin authorisation. Transfer requires admin on both source and destination.
Troubleshooting
iOS Safari Private Mode visitors aren't appearing in Live Activity.
This is the canonical symptom that tells you you need a first-party domain. iOS Safari Private mode applies the most aggressive variant of Apple's tracking protections — it silently blocks navigator.sendBeacon() calls to classified third-party origins. Apex's snippet POSTs events to app.apex.inc from your domain, which iOS classifies as a tracker. The sendBeacon call returns true to the page, but the request never actually leaves the device.
You'll see the same workspace receive events normally from desktop Chrome / Firefox / Edge (including their respective Private / Incognito modes — those don't apply equivalent protections), regular iOS Safari (events flow but visitor IDs cap at 7 days), and Android Chrome — but iOS Private tabs are completely silent.
Setting up a first-party snippet domain converts the whole flow to same-eTLD+1 from Safari's perspective, so the Private-mode tracker classifier doesn't engage and events flow normally. Once your status flips to Active and your install snippet is updated, iOS Private visitors start appearing in Live Activity within seconds.
To confirm the diagnosis: open your site in iOS Safari Private mode while connected to a Mac with Safari Web Inspector enabled (Settings → Safari → Advanced → Web Inspector). In the inspector's Network tab, filter for api/events. If the request is missing entirely or shows as failed/blocked, you're seeing the protection in action.
Status stuck on "DNS pending" for over 24 hours. Almost always a DNS records mismatch. Open the card, copy the validation CNAMEs again, and compare exactly what's in your DNS — even a trailing dot can break ACM validation. ACM gives up at 72 hours, after which the status flips to Failed with a specific reason.
Status went to "Active" but my install still loads from app.apex.inc.
The install copy in the dashboard updates the moment status flips, but you need to deploy that new snippet to your site. Visitors loading the old snippet will keep working — just without the first-party benefits.
iOS Safari still shows the privacy warning. Two reasons it might persist:
- The DNS hasn't fully propagated globally yet — give it an hour.
- Your install hasn't been updated. Check the script src on your live site is now your custom subdomain, not
app.apex.inc.
If neither of those fixes it, contact support — there are some edge cases in older Safari versions where ATFP heuristics flag scripts even on first-party domains.
Cert renewal alert keeps firing. ACM needs the validation CNAMEs to renew. If you removed them after the original issue, re-add them. The next 5-minute sweep will pick them up and clear the alert.