Consenti

Twilio Segment

Segment is a pipe, not a single tracker — analytics.js fans one call out to every downstream tool in your workspace, which makes coarse script-gating a blunt instrument. Consenti gives you a per-call consent breakdown instead, so identify() can stay blocked while page() already flows, or vice versa.

Reading consent in Segment's shape

typescript
const segmentConsent = widget.getConsent('twilio-segment')
// {
//   identify: 'denied',   // ties events to a known user — from cookies with purpose: 'preferences'
//   page: 'granted',      // page-view calls — from cookies with purpose: 'analytics'
//   track: 'granted',     // event tracking — from cookies with purpose: 'analytics'
//   group: 'granted',     // account/org association — from cookies with purpose: 'analytics'
//   alias: 'granted',     // merging anon + known IDs — from cookies with purpose: 'analytics'
// }
ℹ️identify is deliberately split out under the preferences purpose rather than analytics— it's the call that attaches PII (email, user ID, traits) to the visitor, so it's reasonable for a visitor to allow anonymous page/track events while still declining to be identified. See API Methods for the full getConsent(type) reference.

Step 1 — Gate the snippet load

Don't call analytics.load() at all until at least anonymous tracking is allowed — this also means the Segment CDN request itself, and every downstream destination it can trigger, never happens pre-consent. The categoryId below is your own authored category ID (see Category type) — use whichever one covers analytics in your profile:

segment.ts
typescript
import { CategoryScript } from '@consenti/ui'

new CategoryScript({
  categoryId: 'analytics',
  widget,
  unsafeInnerHTML: `
    !function(){var analytics=window.analytics=window.analytics||[];
    if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");
    else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];
    analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};
    for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}
    analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;
    t.src="https://cdn.segment.com/analytics.js/v1/"+key+"/analytics.min.js";
    var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};
    analytics._writeKey="YOUR_WRITE_KEY";analytics.SNIPPET_VERSION="4.16.1";
    analytics.load("YOUR_WRITE_KEY");}}();
  `,
})

Step 2 — Wrap each call site with its own check

Segment's SDK has no built-in consent flag, so per-call gating means checking getConsent('twilio-segment') at the point you call each method. A small wrapper keeps this out of your feature code:

segment-consent.ts
typescript
function segmentConsent() {
  return widget.getConsent('twilio-segment')
}

export function safeIdentify(userId: string, traits?: Record<string, unknown>) {
  if (segmentConsent()?.identify === 'granted') {
    window.analytics?.identify(userId, traits)
  }
}

export function safePage(name?: string, properties?: Record<string, unknown>) {
  if (segmentConsent()?.page === 'granted') {
    window.analytics?.page(name, properties)
  }
}

export function safeTrack(event: string, properties?: Record<string, unknown>) {
  if (segmentConsent()?.track === 'granted') {
    window.analytics?.track(event, properties)
  }
}

Call site usage looks identical to calling Segment directly:

typescript
safeTrack('Product Viewed', { productId: 'sku_123' })
safeIdentify(user.id, { email: user.email, plan: user.plan })
💡Re-evaluating consent per call (rather than once at page load) means a visitor who withdraws consent mid-session immediately stops generating new events, without you having to track subscription state yourself — getConsent() always reflects the latest submission.

Backfilling identify() after consent arrives

A common pattern: track anonymously from page load, then call identify() once the visitor logs in or grants the cookie parameter carrying purpose: 'preferences' — whichever happens second. ConsentAction's id here is a specific cookie ID (unlike CategoryAction/CategoryScript's id/categoryId, which target a whole authored category):

typescript
import { ConsentAction } from '@consenti/ui'

new ConsentAction({
  id: 'preferences_storage', // the cookie ID you gave purpose: 'preferences' in your profile
  widget,
  onGrant: () => {
    if (currentUser) safeIdentify(currentUser.id, { email: currentUser.email })
  },
})

Verifying

  1. Open the browser console and inspect the Segment debugger panel, or run analytics.debug().
  2. Before consenting, confirm no requests to api.segment.io fire.
  3. Accept analytics consent — confirm page/track calls start reaching the debugger, while identifystays absent until you've also granted the category covering your preferences-purpose cookie.

Frequently asked questions