IdentitySelf-Generated IDs

Self-Generated Vendor IDs

Self-generation is Signal’s primary method for establishing vendor identity without loading vendor JavaScript. The Ingestion Gateway generates identifiers in the exact format each vendor expects, sets them as server-side first-party cookies, and stores them in Redis. When events are delivered to vendor APIs, these self-generated IDs are included in the payload — and vendors accept them as valid.

How It Works

Each vendor’s server-side API defines a format for its client identifier. For example, Google Analytics 4’s Measurement Protocol expects a client_id in the format {random_uint32}.{unix_timestamp}. Meta’s Conversions API expects an _fbp value in the format fb.1.{timestamp_ms}.{random_10_digit}.

Signal generates IDs that match these formats exactly. Because the vendor APIs are designed for server-to-server use, they accept any correctly-formatted identifier — there is no validation that the ID was generated by the vendor’s own JavaScript.

The generated IDs are set as first-party cookies on the customer’s subdomain via Set-Cookie response headers. This achieves two goals:

  1. Persistence — the ID survives across sessions and page loads
  2. ITP exemption — server-set cookies are not subject to Safari’s 7-day cap

Supported Vendors

VendorCookie NameFormatTTLNotes
GA4_ga{random_uint32}.{unix_timestamp}2 yearsMeasurement Protocol accepts any client_id
Meta_fbpfb.1.{timestamp_ms}.{random_10_digit}90 daysConversions API accepts self-generated fbp
Meta_fbcfb.1.{timestamp_ms}.{fbclid}90 daysOnly generated when fbclid is present in URL
TikTok_ttpUUID v4 (lowercase, no dashes, 27 chars via nanoid)13 monthsEvents API accepts self-generated ttp
Pinterest_pin_unauthUUID v4365 daysConversions API accepts self-generated value
Snapchat_scidUUID v413 monthsConversions API accepts self-generated value

Generation Details

GA4 (_ga)

Google Analytics 4’s Measurement Protocol requires a client_id parameter. The standard GA4 JavaScript tag generates this as two dot-separated numbers: a random 32-bit unsigned integer and a Unix timestamp.

Format:  {random_uint32}.{unix_timestamp}
Example: 1234567890.1708876543

Signal generates this at the Ingestion Gateway:

clientID := fmt.Sprintf("%d.%d", cryptoRandUint32(), time.Now().Unix())

The Measurement Protocol does not validate whether the client_id was generated by its own tag. Any value in the correct format is accepted as a valid client identifier.

If the standard GA4 tag also runs on the page (during a migration period, for example), both Signal’s server-set _ga cookie and GA4’s JavaScript-set _ga cookie will be present. Signal’s server-set cookie takes precedence because it is set first during the response, and Datafly.js uses the server-set value when sending events.

Meta (_fbp)

Meta’s Conversions API accepts an fbp parameter representing the browser ID. The Meta pixel generates this in a specific format:

Format:  fb.{subdomain_index}.{creation_time_ms}.{random_10_digit}
Example: fb.1.1708876543000.9876543210

Signal generates this as:

fbp := fmt.Sprintf("fb.1.%d.%d", time.Now().UnixMilli(), cryptoRandNDigits(10))

The subdomain index is always 1 for standard implementations. The Conversions API accepts self-generated fbp values — Meta’s own documentation acknowledges that server-side implementations may generate this value independently.

Meta (_fbc)

The _fbc cookie represents a Facebook click ID and is only generated when the fbclid URL parameter is present:

Format:  fb.1.{timestamp_ms}.{fbclid_value}
Example: fb.1.1708876543000.IwAR3x...
if fbclid := queryParams.Get("fbclid"); fbclid != "" {
    fbc := fmt.Sprintf("fb.1.%d.%s", time.Now().UnixMilli(), fbclid)
}
⚠️

The _fbc cookie is only set when a fbclid parameter is found in the URL. It is not generated on requests without a click ID. See Click ID Capture for how click IDs are detected.

TikTok (_ttp)

TikTok’s Events API accepts a ttp parameter for browser identity. The TikTok pixel generates this as a 27-character lowercase alphanumeric string (similar to a UUID v4 without dashes, truncated):

Format:  27-character nanoid (lowercase alphanumeric)
Example: a1b2c3d4e5f6g7h8i9j0k1l2m3n
ttp := nanoid.MustGenerate("0123456789abcdefghijklmnopqrstuvwxyz", 27)

Pinterest (_pin_unauth)

Pinterest’s Conversions API accepts an external_id that maps to the _pin_unauth cookie. The format is a standard UUID v4:

Format:  UUID v4
Example: f47ac10b-58cc-4372-a567-0d02b2c3d479
pinUnauth := uuid.NewV4().String()

Snapchat (_scid)

Snapchat’s Conversions API accepts a uuid_c1 parameter mapped from the _scid cookie. The format is a standard UUID v4:

Format:  UUID v4
Example: b82d5c91-3f7a-4e28-9c8f-1a2b3c4d5e6f
scid := uuid.NewV4().String()

All self-generated IDs are set as server-side cookies in the Ingestion Gateway response. A single response may include multiple Set-Cookie headers:

HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: _dfid=f47ac10b-58cc-4372-a567-0d02b2c3d479; Max-Age=63072000; HttpOnly; Secure; SameSite=Lax; Path=/
Set-Cookie: _ga=1234567890.1708876543; Max-Age=63072000; Secure; SameSite=Lax; Path=/
Set-Cookie: _fbp=fb.1.1708876543000.9876543210; Max-Age=7776000; Secure; SameSite=Lax; Path=/
Set-Cookie: _ttp=a1b2c3d4e5f6g7h8i9j0k1l2m3n; Max-Age=34164000; Secure; SameSite=Lax; Path=/

Note that vendor cookies are not httpOnly — this matches the behaviour of the original vendor tags, which set cookies accessible to JavaScript. The _dfid cookie is the only one marked httpOnly.

Vendor cookies are only set for vendors that have an active integration on the source. If a source only has GA4 and Meta integrations, the gateway will not set _ttp, _pin_unauth, or _scid cookies.

Why This Works

Vendor server-side APIs are explicitly designed for server-to-server event delivery. They accept externally-generated client identifiers because:

  1. Server-side implementations have no browser context — a backend sending events cannot run vendor JavaScript, so the API must accept IDs generated by other means.
  2. The ID is a correlation key, not a credential — vendors use the client ID to group events from the same user, not to authenticate the sender. Any consistent value serves this purpose.
  3. Vendor documentation endorses it — Google, Meta, TikTok, and others explicitly document that server-side implementations should generate their own client identifiers.

The key requirement is consistency: the same ID must be sent with every event from the same visitor. Signal achieves this by persisting the ID in both a first-party cookie (for browser-to-server consistency) and Redis (for server-side lookup during event processing).

Idempotent Generation

The Ingestion Gateway only generates a vendor ID if one does not already exist. On each request:

  1. Read existing vendor cookies from the Cookie header
  2. For each enabled vendor integration on this source:
    • If the vendor cookie exists in the request, use the existing value
    • If the vendor cookie is missing, generate a new one
  3. Store all values (existing and new) in Redis
  4. Set Set-Cookie headers for any new or refreshed cookies

This means a visitor who already has a _ga cookie from a previous visit will continue using the same value. New vendor IDs are only generated when a cookie is missing — either because the visitor is new, or because the cookie expired.