Client-Side Tag Injection
Signal’s default model is server-side delivery: Datafly.js captures events in the browser and sends them to your server, where delivery workers forward data to vendor APIs. No vendor JavaScript runs in the browser.
But some vendors require client-side JavaScript for features that cannot work server-side — push notifications (Braze), chat widgets (Intercom), session replay (Hotjar), and on-page A/B testing (Optimizely). For these vendors, Signal provides two systems to manage their scripts alongside server-side delivery.
Signal never bundles, caches, or proxies vendor JavaScript. Vendor scripts are loaded directly from the vendor’s CDN. Signal orchestrates when they load, what configuration they receive, and what identity is passed to them.
Two systems
Tag Injector (manual)
A low-level API for injecting arbitrary <script> tags into the page. Use this for scripts that don’t need identity integration or lifecycle management — for example, a custom pixel or a vendor not in the Signal catalog.
Hybrid SDK Loader (automatic)
A high-level orchestration system for vendor SDKs that need configuration, identity, and SPA awareness. This is configured in the Management UI per integration and baked into the collector at build time. No code changes are required.
Tag Injector
_df.injectTag(tag)
Injects a script into the page. Returns true if successful, false if rejected (consent denied, capacity reached, or duplicate).
_df.injectTag({
id: 'hotjar',
src: 'https://static.hotjar.com/c/hotjar-123456.js',
consentCategory: 'analytics',
timing: 'post-render',
vendor: 'hotjar',
cookiePrefixes: ['_hj'],
storageKeys: ['_hjSession'],
});_df.removeTag(tag)
Removes a previously injected tag from the DOM. Also clears vendor cookies and localStorage entries matching the configured prefixes/keys.
_df.removeTag({ id: 'hotjar' });TagDefinition
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier for the tag |
src | string | Yes | Full URL of the script to inject |
consentCategory | 'analytics' | 'marketing' | 'functional' | Yes | Consent category required to inject |
timing | 'pre-render' | 'post-render' | Yes | Synchronous (head) or async (body) injection |
vendor | string | No | Vendor name for cleanup identification |
cookiePrefixes | string[] | No | Cookie name prefixes to clear on removal |
storageKeys | string[] | No | localStorage keys to clear on removal |
attributes | Record<string, string> | No | Custom attributes to set on the script element |
Performance guardrails
- Maximum 10 tags — additional tags are rejected with a console warning
- Maximum 1 pre-render tag — synchronous scripts block rendering; only one is permitted
- 100ms stagger — post-render tags are injected 100ms apart to reduce main-thread contention
- Consent re-check — consent is validated both at scheduling time and at actual injection time (for staggered tags, consent may have been revoked in between)
Hybrid SDK Loader
The Hybrid SDK Loader manages the full lifecycle of vendor SDKs that need to run in the browser. It is configured in the Management UI and requires no code changes.
How it works
-
Enable in the Management UI — on the integration’s connection config page, toggle “Enable [Vendor] SDK”. This is only available for vendors whose catalog entry has a
client_sdkmanifest. -
Rebuild the collector — the JS Builder bakes the SDK configuration into the collector. It selects the hybrid bundle (
datafly-hybrid.js, ~1.5KB larger than the core bundle). -
On page load — the collector checks consent and loads consented vendor scripts from their CDNs.
-
Initialisation — the loader calls the vendor’s init function with API keys and configuration from the integration settings.
-
Identity — when
_df.identify()is called, the loader automatically forwards the user ID and traits to each loaded SDK. -
SPA navigation — the loader patches
history.pushStateand listens forpopstateevents. On navigation, it notifies each SDK that has apageChangeFunction. -
Consent revocation — if consent is revoked, the loader calls the vendor’s teardown function, removes the script element, and clears vendor cookies and localStorage.
Supported vendors
| Vendor | SDK | Consent | Use case |
|---|---|---|---|
| Braze | Braze Web SDK | marketing | Push notifications, in-app messaging, content cards |
| Intercom | Intercom Messenger | functional | Chat widget, product tours, help centre |
| Hotjar | Hotjar Tracking Code | analytics | Session replay, heatmaps, surveys |
Phase 2 (planned): FullStory, Optimizely, VWO, Dynamic Yield, HubSpot, Zendesk, Drift.
Example: Braze
When a customer enables the Braze client SDK in the Management UI, Signal will:
- Load
braze.min.jsfrom Braze’s CDN (only whenmarketingconsent is granted) - Call
braze.initialize({ app_id, sdk_endpoint })with the customer’s API key - Call
braze.automaticallyShowInAppMessages()andbraze.openSession() - On
_df.identify(): callbraze.changeUser(userId), then set email, name, phone via individual trait setters - On consent revocation: call
braze.destroy(), remove the script, clearab.*cookies andab.storagelocalStorage
The server-side Braze integration (sending events via the Braze REST API) continues to work alongside the client-side SDK. Server-side handles event delivery and attribution, while client-side handles push notifications, in-app messages, and content cards.
Consent integration
Both the Tag Injector and Hybrid SDK Loader respect your consent configuration:
- Before loading — consent state is checked. If the required category is not granted, the SDK/tag is not loaded.
- On consent change — the Hybrid SDK Loader listens for consent state changes. Newly consented SDKs are loaded; revoked SDKs are torn down with full cleanup.
- On teardown — the vendor’s teardown function is called, the script element is removed, and vendor cookies/localStorage are cleared.
Consent categories are the same as for server-side delivery: analytics, marketing, functional. A vendor’s consent category is defined in its catalog manifest and cannot be overridden per-customer.
Migrating from Tealium IQ or GTM
| Traditional TMS | Signal |
|---|---|
| Loads all vendor tags client-side | Server-side by default; client-side only when necessary |
| Vendor JS bundled or proxied by TMS | Vendor JS loaded from vendor CDN (not bundled) |
| All event delivery via client-side tags | Server-to-server delivery; client SDK only for UI features |
| Consent via TMS consent manager | Consent via CMP integration (OneTrust, Cookiebot, custom) |
| Identity managed per vendor tag | Unified identity: Signal identifies once, all SDKs receive it |
The migration path: replace the TMS container with the Datafly.js collector. Vendors that were purely data collection (GA4, Meta CAPI, etc.) are handled by Signal’s delivery workers with no client-side code. Vendors that need client-side JS (Braze, Intercom, etc.) use Signal’s Hybrid SDK Loader. The result is one script tag instead of a TMS container plus dozens of vendor tags.