Skip to content

Live intent omni channel module #4127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Aug 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
bf8c1fa
WIP - LiveIntent Module
3link Apr 25, 2025
8429f34
Adjust the hook code
3link Apr 25, 2025
459028d
WIP - polish, add config tests
3link Apr 25, 2025
c846d1c
Add unit tests
3link Apr 28, 2025
8348d46
Add auth token
3link Apr 29, 2025
c0f6566
Add README.md
3link May 6, 2025
4f90c17
Fix stage
3link May 6, 2025
c6dc20e
Add logging
3link May 7, 2025
a863d0a
Add docs
3link May 7, 2025
62a4508
Format
3link May 7, 2025
e45ccbc
Impro docs
3link May 7, 2025
3dbb46a
Add IdResResponse decode test
3link May 7, 2025
69fc413
Improve code style
3link May 7, 2025
c1ad22f
Fix Collections API usage
3link May 7, 2025
26288b7
Clean up/format
3link May 14, 2025
4192fa8
Merge branch 'prebid:master' into cm-1776
SuperIzya May 15, 2025
1f1a7cf
Format
3link May 19, 2025
c9659ad
Add treatment rate
3link May 19, 2025
3ed7ee8
Remove superflous JsonProperty annotation
3link Jun 11, 2025
bedb969
Separate field by line
3link Jun 11, 2025
d94e06f
Use RandomGenerator + ThreadLocalRandom instead of Random
3link Jun 11, 2025
61ceeed
Apply code style
3link Jun 11, 2025
fa66d18
Apply style guide: method order
3link Jun 11, 2025
09450d2
Add empty lines to separate test stages
3link Jun 11, 2025
b588f6a
Use NoArgsConstructor instead of Jacksonized
3link Jun 11, 2025
1ae6da8
Merge branch 'master' into cm-1776
3link Jun 11, 2025
f597339
Bump dependency version
3link Jun 11, 2025
5f15bf1
cm-1776: PR issues fixed
Jul 29, 2025
d6dfcc7
Merge branch 'master' into cm-1776
Aug 5, 2025
53ca621
Merge branch 'master' into cm-1776
Aug 5, 2025
b61986a
cm-1776: PR issues fixed
Aug 5, 2025
e458a4c
cm-1776: PR issues fixed
Aug 5, 2025
bf7d54a
LiveIntent Omni Channel Module Refactoring
AntoxaAntoxic Aug 8, 2025
88d0bc8
Fix checkstyle
AntoxaAntoxic Aug 8, 2025
c0901b3
Merge remote-tracking branch 'origin/master' into live-intent-omni-ch…
AntoxaAntoxic Aug 8, 2025
53a6e6f
Fix version
AntoxaAntoxic Aug 8, 2025
4d6a603
Fix comments
AntoxaAntoxic Aug 8, 2025
cc3e092
fix comments
AntoxaAntoxic Aug 12, 2025
d8cb2fa
Fix comments
AntoxaAntoxic Aug 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions extra/bundle/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
<artifactId>wurfl-devicedetection</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.prebid.server.hooks.modules</groupId>
<artifactId>live-intent-omni-channel-identity</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
Expand Down
46 changes: 46 additions & 0 deletions extra/modules/live-intent-omni-channel-identity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Overview

This module enriches bid requests with user EIDs.

The user EIDs to be enriched are configured per partner as part of the LiveIntent HIRO onboarding process. As part of this onboarding process, partners will also be provided with the `identity-resolution-endpoint` URL as well as with the `auth-token`.

`treatment-rate` is a value between 0.0 and 1.0 (including 0.0 and 1.0) and defines the percentage of requests for which identity enrichment should be performed. This value can be freely picked. We recommend a value between 0.9 and 0.95

## Configuration

To start using the LiveIntent Omni Channel Identity module you have to enable it and add configuration:

```yaml
hooks:
liveintent-omni-channel-identity:
enabled: true
host-execution-plan: >
{
"endpoints": {
"/openrtb2/auction": {
"stages": {
"processed-auction-request": {
"groups": [
{
"timeout": 100,
"hook-sequence": [
{
"module-code": "liveintent-omni-channel-identity",
"hook-impl-code": "liveintent-omni-channel-identity-enrichment-hook"
}
]
}
]
}
}
}
}
}
modules:
liveintent-omni-channel-identity:
request-timeout-ms: 2000
identity-resolution-endpoint: "https://liveintent.com/idx"
auth-token: "secret-token"
treatment-rate: 0.9
```

18 changes: 18 additions & 0 deletions extra/modules/live-intent-omni-channel-identity/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.prebid.server.hooks.modules</groupId>
<artifactId>all-modules</artifactId>
<version>3.31.0-SNAPSHOT</version>
</parent>

<artifactId>live-intent-omni-channel-identity</artifactId>

<name>live-intent-omni-channel-identity</name>
<description>LiveIntent Omni-Channel Identity</description>

<dependencies>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.config;

import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.LiveIntentOmniChannelProperties;
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.LiveIntentOmniChannelIdentityModule;
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.hooks.LiveIntentOmniChannelIdentityProcessedAuctionRequestHook;
import org.prebid.server.hooks.v1.Module;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.vertx.httpclient.HttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Collections;

@Configuration
@ConditionalOnProperty(
prefix = "hooks." + LiveIntentOmniChannelIdentityModule.CODE,
name = "enabled",
havingValue = "true")
public class LiveIntentOmniChannelIdentityConfiguration {

@Bean
@ConfigurationProperties(prefix = "hooks.modules." + LiveIntentOmniChannelIdentityModule.CODE)
LiveIntentOmniChannelProperties properties() {
return new LiveIntentOmniChannelProperties();
}

@Bean
Module liveIntentOmniChannelIdentityModule(LiveIntentOmniChannelProperties properties,
JacksonMapper mapper,
HttpClient httpClient,
@Value("${logging.sampling-rate:0.01}") double logSamplingRate) {

final LiveIntentOmniChannelIdentityProcessedAuctionRequestHook hook =
new LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(
properties, mapper, httpClient, logSamplingRate);

return new LiveIntentOmniChannelIdentityModule(Collections.singleton(hook));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model;

import com.iab.openrtb.request.Eid;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class IdResResponse {

List<Eid> eids;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config;

import lombok.Data;

@Data
public final class LiveIntentOmniChannelProperties {

long requestTimeoutMs;

String identityResolutionEndpoint;

String authToken;

float treatmentRate;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1;

import org.prebid.server.hooks.v1.Hook;
import org.prebid.server.hooks.v1.InvocationContext;
import org.prebid.server.hooks.v1.Module;

import java.util.Collection;

public record LiveIntentOmniChannelIdentityModule(
Collection<? extends Hook<?, ? extends InvocationContext>> hooks) implements Module {

public static final String CODE = "liveintent-omni-channel-identity";

@Override
public String code() {
return CODE;
}

@Override
public Collection<? extends Hook<?, ? extends InvocationContext>> hooks() {
return hooks;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.hooks;

import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Eid;
import com.iab.openrtb.request.User;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import org.apache.commons.collections4.ListUtils;
import org.prebid.server.hooks.execution.v1.InvocationResultImpl;
import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl;
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.IdResResponse;
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.LiveIntentOmniChannelProperties;
import org.prebid.server.hooks.v1.InvocationAction;
import org.prebid.server.hooks.v1.InvocationResult;
import org.prebid.server.hooks.v1.InvocationStatus;
import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;
import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
import org.prebid.server.hooks.v1.auction.ProcessedAuctionRequestHook;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.log.ConditionalLogger;
import org.prebid.server.log.LoggerFactory;
import org.prebid.server.util.HttpUtil;
import org.prebid.server.util.ListUtil;
import org.prebid.server.vertx.httpclient.HttpClient;
import org.prebid.server.vertx.httpclient.model.HttpClientResponse;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;

public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements ProcessedAuctionRequestHook {

private static final ConditionalLogger conditionalLogger = new ConditionalLogger(LoggerFactory.getLogger(
LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.class));

private static final String CODE = "liveintent-omni-channel-identity-enrichment-hook";

private final LiveIntentOmniChannelProperties config;
private final JacksonMapper mapper;
private final HttpClient httpClient;
private final double logSamplingRate;

public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(LiveIntentOmniChannelProperties config,
JacksonMapper mapper,
HttpClient httpClient,
double logSamplingRate) {

this.config = Objects.requireNonNull(config);
HttpUtil.validateUrlSyntax(config.getIdentityResolutionEndpoint());
this.mapper = Objects.requireNonNull(mapper);
this.httpClient = Objects.requireNonNull(httpClient);
this.logSamplingRate = logSamplingRate;
}

@Override
public Future<InvocationResult<AuctionRequestPayload>> call(AuctionRequestPayload auctionRequestPayload,
AuctionInvocationContext invocationContext) {

return config.getTreatmentRate() > ThreadLocalRandom.current().nextFloat()
? requestIdentities(auctionRequestPayload.bidRequest())
.<InvocationResult<AuctionRequestPayload>>map(this::update)
.onFailure(throwable -> conditionalLogger.error(
"Failed enrichment: %s".formatted(throwable.getMessage()), logSamplingRate))
: noAction();
}

private Future<IdResResponse> requestIdentities(BidRequest bidRequest) {
return httpClient.post(
config.getIdentityResolutionEndpoint(),
headers(),
mapper.encodeToString(bidRequest),
config.getRequestTimeoutMs())
.map(this::processResponse);
}

private MultiMap headers() {
return MultiMap.caseInsensitiveMultiMap()
.add(HttpUtil.AUTHORIZATION_HEADER, "Bearer " + config.getAuthToken());
}

private IdResResponse processResponse(HttpClientResponse response) {
return mapper.decodeValue(response.getBody(), IdResResponse.class);
}

private static Future<InvocationResult<AuctionRequestPayload>> noAction() {
return Future.succeededFuture(InvocationResultImpl.<AuctionRequestPayload>builder()
.status(InvocationStatus.success)
.action(InvocationAction.no_action)
.build());
}

private InvocationResultImpl<AuctionRequestPayload> update(IdResResponse resolutionResult) {
return InvocationResultImpl.<AuctionRequestPayload>builder()
.status(InvocationStatus.success)
.action(InvocationAction.update)
.payloadUpdate(payload -> updatedPayload(payload, resolutionResult.getEids()))
.build();
}

private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayload, List<Eid> resolvedEids) {
final List<Eid> eids = ListUtils.emptyIfNull(resolvedEids);
final BidRequest bidRequest = requestPayload.bidRequest();
final User updatedUser = Optional.ofNullable(bidRequest.getUser())
.map(user -> user.toBuilder().eids(ListUtil.union(ListUtils.emptyIfNull(user.getEids()), eids)))
.orElseGet(() -> User.builder().eids(eids))
.build();

return AuctionRequestPayloadImpl.of(bidRequest.toBuilder().user(updatedUser).build());
}

@Override
public String code() {
return CODE;
}
}
Loading
Loading