Server-Proxied Enrichment
Some identity vendors require API credentials (API keys, tokens, or secrets) to resolve an identity. These credentials cannot be exposed to the browser. Signal handles this through server-proxied enrichment: the browser initiates the request, the Ingestion Gateway adds credentials server-side, calls the vendor API, stores the result in Redis, and returns the ID to the browser for local caching.
Why Server Proxying Is Needed
Self-generated IDs and click ID capture cover the majority of vendor identity needs. However, some identity vendors — particularly data onboarders and identity graph providers — require an authenticated API call to resolve an identity:
- The API requires an API key, secret, or bearer token
- The API may require hashed PII (email, phone) that must be sent from a trusted server
- The API response contains sensitive identity tokens that should be stored securely
In all these cases, the browser cannot call the API directly because doing so would expose credentials in client-side code. Signal solves this by proxying the request through the Ingestion Gateway.
Enrichment Flow
1. Browser (Datafly.js)
→ Sends enrichment request to Ingestion Gateway
POST /v1/enrich
{ "vendor": "liveramp_ats", "anonymous_id": "f47ac10b..." }
2. Ingestion Gateway
→ Reads vendor credentials from encrypted config (PostgreSQL)
→ Adds credentials to the request
→ Calls vendor API server-side:
POST https://api.rlcdn.com/api/identity/v2/envelope
Authorization: Bearer {api_key}
{ "identifiers": [...] }
3. Vendor API responds
→ { "envelope": "XY1000bGluZWRpbjANCm..." }
4. Ingestion Gateway
→ Stores result in Redis immediately:
HSET identity:{anonymous_id} ramp_id "XY1000bGluZWRpbjANCm..."
→ Returns ID to browser:
{ "vendor": "liveramp_ats", "id": "XY1000bGluZWRpbjANCm..." }
5. Browser
→ Stores ID in IndexedDB for local caching
→ ID is available in Redis for the next event (no round-trip delay)Dual-Storage Advantage
The server-proxied enrichment pattern stores the identity result in two places simultaneously:
| Storage | Location | Purpose |
|---|---|---|
| Redis | Server-side (identity:{anonymous_id}) | Immediately available for event processing |
| IndexedDB | Browser-side (Datafly.js) | Avoids redundant enrichment calls on subsequent page loads |
This dual-storage approach eliminates the “one-event delay” problem that plagues other server-side tag managers. In a naive implementation, the enrichment result would only be available after the browser sends another event. With Signal’s approach, the Ingestion Gateway stores the result in Redis immediately after receiving it from the vendor — so even if the current page’s events have already been processed, the enrichment result is available for any events that arrive milliseconds later.
On subsequent page loads, Datafly.js checks IndexedDB before requesting enrichment. If a valid (non-expired) result exists locally, no enrichment call is made.
Supported Vendors
Acxiom RealID
| Property | Value |
|---|---|
| Endpoint | api.us-east-1.a.realid.rapid.acxiom.io |
| Authentication | API key (header) |
| Input | Hashed email or device IDs |
| Output | Acxiom Person ID + demographic attributes |
| Redis field | acxiom_person_id |
| TTL | 30 days |
Acxiom RealID resolves anonymous visitors to Acxiom’s identity graph, returning a persistent Person ID and optional demographic attributes. This enables deterministic audience matching across channels.
Amperity
| Property | Value |
|---|---|
| Endpoint | Customer-specific (e.g. api.amperity.com/v1/resolve) |
| Authentication | Bearer token |
| Input | Anonymous ID + any known PII |
| Output | Amperity unified ID |
| Redis field | amperity_id |
| TTL | 30 days |
Amperity provides a customer data platform with identity resolution. The enrichment call resolves a Signal anonymous ID to an Amperity unified customer ID, enabling event delivery to be matched against Amperity’s unified customer profiles.
LiveRamp ATS
| Property | Value |
|---|---|
| Endpoint | api.rlcdn.com |
| Authentication | API key (header) |
| Input | Hashed email (SHA-256) |
| Output | RampID envelope (encrypted) |
| Redis field | ramp_id |
| TTL | 24 hours (envelope expiry) |
LiveRamp’s Authenticated Traffic Solution (ATS) resolves hashed email addresses to RampID envelopes. RampIDs enable programmatic advertising use cases and are accepted by demand-side platforms for audience targeting.
LiveRamp envelopes are short-lived (24 hours). Signal automatically refreshes the envelope on subsequent visits. The Redis TTL for ramp_id is set to match the envelope expiry.
UID2
| Property | Value |
|---|---|
| Endpoint | UID2 operator endpoint (region-specific) |
| Authentication | API key + client secret |
| Input | Hashed email or phone (SHA-256, normalised) |
| Output | UID2 token (encrypted, rotatable) |
| Redis field | uid2_token |
| TTL | Matches token refresh interval |
Unified ID 2.0 is an open-source identity framework for the advertising ecosystem. Signal calls the UID2 operator to generate a UID2 token from hashed PII, which can then be included in programmatic ad requests.
The Trade Desk
| Property | Value |
|---|---|
| Endpoint | match.adsrvr.org/track/rid |
| Authentication | Partner ID + secret |
| Input | Hashed email (SHA-256) or device IDs |
| Output | TDID (The Trade Desk ID) |
| Redis field | tdid |
| TTL | 30 days |
The Trade Desk ID (TDID) enables conversion attribution and audience targeting on The Trade Desk’s demand-side platform.
Generic Enrichment Pattern
In addition to the built-in vendor integrations, Signal supports custom enrichment endpoints. This allows customers to add their own identity providers or internal identity services using the same server-proxied pattern.
Custom enrichment endpoints are configured per source:
{
"enrichment": {
"custom_providers": [
{
"name": "internal_cdp",
"endpoint": "https://cdp.internal.example.com/v1/resolve",
"method": "POST",
"authentication": {
"type": "bearer",
"token_secret_ref": "secret:cdp_api_token"
},
"request_template": {
"anonymous_id": "{{anonymous_id}}",
"email_hash": "{{sha256_email}}"
},
"response_mapping": {
"redis_field": "cdp_customer_id",
"json_path": "$.customer_id"
},
"ttl": "30d",
"timeout": "2s"
}
]
}
}The gateway interpolates template variables ({{anonymous_id}}, {{sha256_email}}, etc.) from the event context, calls the endpoint with the configured authentication, extracts the result using the JSON path expression, and stores it in Redis under the specified field name.
Custom enrichment endpoints must respond within the configured timeout (default 2 seconds). If the endpoint is slow or unavailable, the enrichment is skipped for that request and retried on the next visit.
Credential Security
Vendor API credentials are stored encrypted in PostgreSQL using AES-256-GCM encryption. The encryption key is provided via environment variable and is never stored in the database.
Credential storage:
PostgreSQL → encrypted blob (AES-256-GCM)
Encryption key → DATAFLY_ENCRYPTION_KEY environment variable
Credential flow:
Management UI → Management API → encrypt → PostgreSQL
Ingestion Gateway → PostgreSQL → decrypt → vendor API call
Browser → never sees credentialsCredentials are decrypted in memory at the Ingestion Gateway only when making a vendor API call. They are never included in event payloads, Kafka messages, logs, or browser responses.
Never log or expose enrichment API credentials. Signal’s logging is configured to redact credential values automatically. If you add custom enrichment endpoints, ensure your endpoint URLs do not contain credentials as query parameters.
Enrichment Timing
Server-proxied enrichment is asynchronous relative to the initial page event. The typical sequence is:
1. Page loads → Datafly.js sends page event (immediate)
2. Datafly.js checks IndexedDB for cached enrichment results
3. If no cached result → Datafly.js sends enrichment request to gateway
4. Gateway proxies to vendor API → stores result in Redis → returns to browser
5. Browser stores result in IndexedDB for future visitsSteps 1 and 3 happen in parallel. The page event is not blocked by the enrichment call. This means:
- The first event from a new visitor may not have enrichment data (it is still in-flight)
- The enrichment result is stored in Redis as soon as the vendor responds
- Subsequent events from the same session (or future sessions) include the enrichment data
For most use cases, this latency is acceptable because conversion events (purchases, sign-ups) typically happen later in the session, by which time the enrichment result is available.
Error Handling
If a vendor enrichment call fails, Signal handles it gracefully:
| Scenario | Behaviour |
|---|---|
| Vendor API timeout (>2s) | Enrichment skipped, retried on next visit |
| Vendor API returns error (4xx/5xx) | Logged, skipped, retried on next visit |
| Invalid credentials | Logged as configuration error, not retried until credentials are updated |
| Vendor API rate limit (429) | Backed off, retried after Retry-After interval |
| Network error | Enrichment skipped, retried on next visit |
Failed enrichment calls never block event processing. The event is processed and delivered with whatever identity data is available. The enrichment result will be included in future events once the vendor API responds successfully.