Bloomreach
Commerce-focused customer data and experience platform combining real-time customer data, AI-driven personalisation, and marketing automation.
Signal delivers events into a Bloomreach Data Hub event stream. Data Hub is Bloomreach’s unified ingestion layer: one stream validates incoming events against its own permission rules and then fans them out to whichever Bloomreach products are connected to it (Marketing/Engagement, Search).
If your Bloomreach project predates Data Hub and is still addressed by project token, use the Bloomreach Engagement (legacy) integration instead. Data Hub credentials and project-token credentials are not interchangeable.
Why route Bloomreach through Datafly Signal
- Profiles are updated from the server, not the browser. Events reach Bloomreach even when a tracker blocker or a failed page unload would have dropped a client-side call, so the profile reflects what actually happened.
- Client IP and user agent are preserved. Server-to-server delivery normally loses both, which weakens matching. Signal forwards the genuine visitor values as request headers so Bloomreach attributes the event to the right session.
- Delivery is batched, rate-limited, and retried. Signal buffers to Kafka, respects the Bloomreach rate limit, and retries on 429 or transient failure rather than dropping the event.
- Presets for Retail and General (all verticals). The mapping starts from a working configuration for your vertical instead of a blank field map.
- One consent decision governs every destination. Bloomreach is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.
Prerequisites
Complete these steps in Bloomreach before configuring Signal.
Create a server-side event stream
In Bloomreach, go to Data hub > Events and create an event stream, or open an existing one. A stream used by Signal must be a private stream: private streams authenticate with a secret key held by your backend, which is what a server-side pipeline needs. Public streams authenticate with the stream ID alone and are intended for browser and mobile SDKs.
Copy the Stream ID and Target
Open the stream and go to the Access security tab. The API Credentials section shows:
- Stream ID — identifies which stream receives your events.
- Target — the API base URL for your workspace (default
https://api-engagement.bloomreach.com; it is region-specific, so use the value shown rather than assuming the default).
Create a shared secret key
Still on Access security, in the API authentication keys section, click + Create new key. Bloomreach shows the Key ID and the key secret.
The key secret is displayed once. Copy it immediately. If you lose it, rotate the key rather than trying to recover it.
Allow the customer IDs and properties Signal sends
On the same tab, the Permissions section controls what the stream will accept. This is the most common cause of “events delivered, nothing in Bloomreach”: a rejected identifier or property is dropped by the stream, not reported as a delivery failure.
Set to Allow at minimum:
- Customer IDs —
registered(the authenticated ID) andcookie(the pseudonymous ID Signal uses to stitch pre-login activity). - Event types — every event the blueprint sends:
view_other,view_category,view_item,search_submit,view_search_results,cart_update,purchase. - Customer properties — any profile attribute you expect the stream to update.
Leaving undefined customer properties set to Deny is good practice, but it means any property not on the list is silently discarded — so add each one you intend to use.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
target_base_url | string | Yes | The Target host, without the https:// prefix (e.g. api-engagement.bloomreach.com). |
stream_id | string | Yes | The Stream ID from the stream’s Access security tab. |
api_key_id | string | Yes | Key ID of the shared secret key. Sent as the HTTP Basic username. |
api_secret | secret | Yes | The key secret. Sent as the HTTP Basic password. |
Signal Setup
Quick Setup
- Navigate to Integrations in the sidebar.
- Open the Integration Library tab.
- Find Bloomreach under the CDP category.
- Click Install, and fill in the required fields with the credentials gathered above.
- Click Install Integration to create the integration with a ready-to-use default configuration.
API Setup
curl -X POST http://localhost:8084/v1/admin/integration-catalog/bloomreach_cdp/install \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Bloomreach",
"variant": "default",
"config": {
"target_base_url": "api-engagement.bloomreach.com",
"stream_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"api_key_id": "xxxxxxxxxxxxxxxxxxxx",
"api_secret": "xxxxxxxxxxxxxxxxxxxx"
},
"delivery_mode": "server_side"
}'Event Mapping
Bloomreach Data Hub defines its own required-event taxonomy, which is page-type specific rather than action-generic. The blueprint maps Signal’s canonical events onto it:
| Signal Event | Bloomreach event_type | Notes |
|---|---|---|
page | view_other, or typed from page_type | General preset only. Not mapped in Retail. See below. |
consent | consent | Disabled by default. Communications consent only. See Consent. |
view_item_list | view_category | item_list_id → category_id, item_list_name → title |
view_item | view_item | Product detail view — product_id, title, variant_id, price |
search | search_submit | search_term → q |
view_search_results | view_search_results | search_term, results_count |
add_to_cart | cart_update (action: add) | Changed item flat, plus product_list |
remove_from_cart | cart_update (action: remove) | Changed item flat, plus product_list |
purchase | purchase | transaction_id → purchase_id, value → total_price, items → product_list |
Signal’s items array is reshaped into Bloomreach’s product_list shape (product_id, variant_id, title, price, quantity).
Page views: pick the preset that matches your site
Bloomreach’s view_* family is its page-view taxonomy, and each member maps to a Search ptype. view_other means “a page that isn’t one of the other types”, so it is a real page type rather than a generic fallback. That makes it important to send exactly one page-view event per page load, correctly typed.
Signal ships two presets because the right way to do that differs by vertical.
Retail uses the explicit ecommerce events, since view_item and view_item_list already carry the page type:
| Signal | Bloomreach |
|---|---|
view_item | view_item |
view_item_list | view_category |
view_search_results | view_search_results |
page is unmapped here. It fires automatically on every page load, so mapping it as well would count every product and listing page twice, with two conflicting page types.
General (all verticals) is for publishing, media, SaaS, finance, travel, and anything else without ecommerce events. Here page is the single page-view event, typed from the optional page_type property:
datafly.page({ page_type: 'content' }) // → Bloomreach view_content, Search ptype=contentpage_type | Bloomreach event_type |
|---|---|
homepage | view_homepage |
category | view_category |
product | view_item |
search | view_search_results |
content | view_content |
thematic | view_thematic |
other / absent / unrecognised | view_other |
The mapping has no wildcard, so an absent or unknown page_type falls back to view_other rather than mislabelling the page. A site sending no page_type at all still gets valid page views, just all classified as “other”.
Consent
Bloomreach consent is communications consent, the permission to contact someone on a channel. It is not cookie consent. Wiring a CMP category to it means a visitor declining analytics cookies is unsubscribed from your email programme, and Bloomreach treats a withdrawal as immediate.
Bloomreach records consent as a first-class event with action, category, valid_until, and a timestamp that becomes the consent’s valid_from. An invalid consent event revokes the customer’s consent, by Bloomreach’s explicit design, on the basis that if it cannot tell whether to accept or reject it fails to the safe side.
The blueprint ships a consent mapping, disabled by default, built around that:
| Bloomreach property | Source | Guard |
|---|---|---|
category | consent_category | Required. No category means the event is dropped by Signal rather than sent invalid. |
action | consent_action | Value-mapped granted→accept, denied→reject, with no wildcard, so an unexpected value can never produce the wrong decision. |
valid_until | static unlimited | Bloomreach requires this whenever action is accept. Override the mapping to send an expiry. |
message | consent_message | Optional to Bloomreach, but it is what makes the record defensible to a regulator. |
Enable it only when you have decided Signal owns a specific Bloomreach consent category, captured from a Signal-instrumented touchpoint such as a newsletter signup or preference centre. See the consent event spec for how to emit it.
Signal is one consent source among several (source: private_api in Bloomreach’s record). It cannot see the email channel, so unsubscribes that happen in an email footer will not reach Signal. Leave Bloomreach’s own list_unsubscribe, consent page, and double opt-in mechanisms in place to cover those.
cart_update expects a full cart snapshot. Bloomreach treats product_list on cart_update as the complete contents of the cart after the change, not just the item that changed. Signal’s add_to_cart and remove_from_cart carry only the changed items, so if you rely on Bloomreach’s cart state, send the full post-change cart in items on those events.
Currency. Bloomreach expects total_price in the project’s reference currency, with local_currency and price_local_currency describing the local one. The blueprint maps value to both, which is correct only when your site’s currency is the project reference currency. If it isn’t, supply a converted value.
Identity
Signal forwards the following identifiers to Bloomreach customer_ids:
| Field | Source | Notes |
|---|---|---|
registered | user_id from datafly.identify() | The authenticated customer ID, and Bloomreach’s hard identifier. Many Bloomreach projects use the email address here — if yours does, map email to registered. |
cookie | Datafly anonymous identifier | Browser-scoped pseudonymous ID so pre-login activity stitches to the profile once the user logs in. |
Call datafly.identify() on login or form submission to maximise profile unification:
datafly.identify("user-123", { email: "jane@example.com" });Every identifier must also be allowed on the stream’s Customer IDs permission list. An identifier that Signal sends but the stream denies is dropped by Bloomreach, and the delivery still returns success.
Anonymous visitors
Anonymous identity is configured in Bloomreach, in two separate places. Both have to be right, and neither reports a problem to Signal when it isn’t.
1. The project’s identifier definitions
Bloomreach distinguishes two identifier types:
| Type | Purpose | Signal maps |
|---|---|---|
| Soft ID | Cookie-scoped, one per device/browser. Anonymous visitors have only this. | cookie ← Datafly anonymous ID (_dfid) |
| Hard ID | The durable identity, usually email or an internal customer ID. | registered ← user_id |
Bloomreach creates a profile for every first-time visitor keyed on the soft ID, then links that history to the hard ID when the visitor logs in or purchases. That is the mechanism which makes pre-login activity count, so the cookie mapping is what makes anonymous attribution work at all — it is marked required in the blueprint for that reason.
Identifier changes are not self-service and not reversible. You cannot delete or rename an identifier once it is set, and you cannot convert a soft ID into a hard ID. Adding any new hard or soft ID to an existing project requires a request to your Bloomreach Customer Success Manager. Signal therefore maps to the standard cookie soft ID, which every project has by default — no Bloomreach request needed. Only ask for a dedicated custom soft ID if you have a specific reason to keep Signal’s identity space separate.
Two limits worth knowing before you design around soft IDs:
- 64 soft IDs per profile, per type. Beyond that Bloomreach discards the least-recently-used one together with its data. A visitor churning cookies (private browsing, many devices) can silently lose history.
- Hard IDs are case-sensitive.
Jane@example.comandjane@example.combecome two profiles. Normalise to lowercase before sending — Signal’snormalised_sha256convention already trims and lowercases, and the same discipline applies here even though Bloomreach wants the raw value rather than a hash.
2. The event stream’s Customer IDs permissions
Separately, each event stream has an allow-list. Under Data hub > Events > your stream > Access security > Permissions > Customer IDs, every identifier is set to Allow, Signed only, or Deny, with a default for identifiers not on the list.
A newly created stream commonly lists only registered. If cookie is absent and the undefined-ID default is Deny, then every pre-login event arrives with no usable identifier — Bloomreach discards it, and the API still returns HTTP 200. Nothing in Signal’s Live Events view will look wrong.
So: add cookie to the Customer IDs list and set it to Allow. Leave undefined IDs denied — that is good hygiene, it just means the list has to be complete.
Signed only requires a JWT from your backend proving the identifier. It is aimed at public streams used by browser SDKs, where a client could otherwise forge a registered ID. Signal delivers over a private stream authenticated with a shared secret, so Allow is the correct setting and JWT signing is not needed.
Why this is server-side in the first place
Bloomreach documents three ways to keep anonymous identity durable, and names “server-side handling of identification of anonymous users by your infrastructure” as its recommended option for organisations with strong security, privacy, and compliance requirements. That is precisely what Signal does — it is the productised version of the approach Bloomreach otherwise expects you to build and maintain yourself.
The problem all three options address is browser cookie lifetime. Safari’s ITP caps JavaScript-set cookies at 7 days, or 24 hours where the domain is classed as a cross-site tracker, so a returning visitor reappears as a brand-new anonymous profile and their pre-login history fragments. Bloomreach’s own guidance is explicit that a CNAME-based tracking domain does not escape this — only NS-zone DNS records do. This is the same reason Signal requires its collect domain to be an A record, never a CNAME: the identity cookie is set server-side, first-party, on a host the browser has no reason to treat as third-party.
Running the Bloomreach Web SDK alongside Signal creates two anonymous profiles. Adding a server-side identity does not disable the Web SDK’s own cookie, and Bloomreach treats the two soft IDs as separate anonymous profiles that only merge once a hard ID ties them together. If you are moving from the Web SDK to Signal, retire the SDK’s tracking rather than running both, or expect duplicate anonymous profiles and split pre-login attribution until login.
Bloomreach Search
If Search is connected to the stream, two additional pieces of data matter:
_br_uid_2— the Bloomreach Search cookie. Bloomreach requires it for server-side purchase attribution. The Bloomreach Web SDK sets it browser-side; a server-side pipeline can only forward it, so pass it through as abr_uid_2event property and the blueprint maps it tometadata._br_uid_2.domain_keyandview_id— Search routing values specific to your workspace. Add them as static mappings tometadata.domain_keyandmetadata.view_idin the integration’s field mappings.
Signal also forwards the visitor’s IP and User-Agent as X-Forwarded-For and User-Agent headers. Bloomreach reads geolocation from X-Forwarded-For and cannot infer it from the connecting server’s IP, so these are required for accurate location reporting and device audience targeting.
Consent
Bloomreach events should typically be sent under the marketing consent category. Bloomreach also exposes its own consent management settings within campaigns — Signal forwards the visitor’s CMP consent state on every event so Bloomreach can honour campaign-level suppression rules.
Delivery
Events are delivered server-side from your Datafly Signal infrastructure directly to the Data Hub tracking API. No client-side scripts are loaded for this integration.
The tracking API is a batch command endpoint, so Signal batches events into a single request:
POST https://TARGET/track/u/v1/batch?stream_id=STREAM_ID
Authorization: Basic base64(KEY_ID:SECRET)
{"commands":[{"name":"customers/events","data":{ ... }}]}Verify it’s working
- After installing the integration, trigger a test event from your website or via the Signal event API.
- In Signal, check Live Events to confirm delivery with a
200response status. - In Bloomreach, open the event stream and check its delivery/diagnostics view to confirm the stream accepted the event rather than rejecting it on a permission rule.
- In the connected Bloomreach project, navigate to Data & Assets > Events and search for recent events.
- Search for the test customer in Customers to verify the profile was created or updated.
Troubleshooting
| Symptom | Possible Cause | Resolution |
|---|---|---|
401 with access key not provided | Credentials missing or sent in the wrong scheme | The endpoint accepts HTTP Basic only. Confirm api_key_id and api_secret are both set. |
401 with access key not found | Key ID or secret wrong, or the key was deleted/rotated | Recreate the key under Access security > API authentication keys and update the integration. |
403 Forbidden | The key’s API group is not scoped to this stream | Use the private API group Data Hub created for the stream, not a group created for item collections. |
404 Not Found | Wrong target_base_url | Use the exact Target value from the stream’s Access security tab — it is region-specific. |
400 Bad Request | Malformed envelope, or missing customer_ids / event_type | Confirm at least one customer ID mapping resolves. cookie is mapped as required for this reason. |
HTTP 200 but {"success": false} | Bloomreach accepted the request but failed to process the command | Read the error field in the response body; Signal classifies this as a failure. |
| Events delivered, nothing appears in Bloomreach | The stream rejected the identifier, property, or event type on a permission rule | Check the stream’s Permissions section. Denied items are dropped silently. |
| Anonymous visitors create no profile, or pre-login activity never links to the customer | cookie is not on the stream’s Customer IDs allow-list, so anonymous events arrive with no usable identifier | See Anonymous visitors. Add cookie and set it to Allow. |
| Duplicate anonymous profiles | The Bloomreach Web SDK is still running alongside Signal, producing a second soft ID | Retire the Web SDK’s tracking rather than running both. |
| Purchases tracked but not attributed in Search | _br_uid_2 missing | Forward the _br_uid_2 cookie from the browser as a br_uid_2 event property. |
| Geolocation or device targeting inaccurate | Client IP/UA not reaching Bloomreach | Confirm the events carry context.ip and context.user_agent; Signal sends them as X-Forwarded-For and User-Agent. |
Visit the Bloomreach Data Hub Tracking API reference for the full API contract, and event stream security and permissions for the permission model.
See also
- Lytics — behavioural-scoring CDP
- Salesforce CDP — enterprise CDP
- Klaviyo — email and SMS marketing