Skip to content

Commit ee2e16f

Browse files
Live intent omni channel module (#4127)
1 parent 0fe0e27 commit ee2e16f

File tree

10 files changed

+484
-0
lines changed

10 files changed

+484
-0
lines changed

extra/bundle/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@
6565
<artifactId>wurfl-devicedetection</artifactId>
6666
<version>${project.version}</version>
6767
</dependency>
68+
<dependency>
69+
<groupId>org.prebid.server.hooks.modules</groupId>
70+
<artifactId>live-intent-omni-channel-identity</artifactId>
71+
<version>${project.version}</version>
72+
</dependency>
6873
</dependencies>
6974

7075
<build>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Overview
2+
3+
This module enriches bid requests with user EIDs.
4+
5+
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`.
6+
7+
`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
8+
9+
## Configuration
10+
11+
To start using the LiveIntent Omni Channel Identity module you have to enable it and add configuration:
12+
13+
```yaml
14+
hooks:
15+
liveintent-omni-channel-identity:
16+
enabled: true
17+
host-execution-plan: >
18+
{
19+
"endpoints": {
20+
"/openrtb2/auction": {
21+
"stages": {
22+
"processed-auction-request": {
23+
"groups": [
24+
{
25+
"timeout": 100,
26+
"hook-sequence": [
27+
{
28+
"module-code": "liveintent-omni-channel-identity",
29+
"hook-impl-code": "liveintent-omni-channel-identity-enrichment-hook"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
}
36+
}
37+
}
38+
}
39+
modules:
40+
liveintent-omni-channel-identity:
41+
request-timeout-ms: 2000
42+
identity-resolution-endpoint: "https://liveintent.com/idx"
43+
auth-token: "secret-token"
44+
treatment-rate: 0.9
45+
```
46+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.prebid.server.hooks.modules</groupId>
7+
<artifactId>all-modules</artifactId>
8+
<version>3.31.0-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>live-intent-omni-channel-identity</artifactId>
12+
13+
<name>live-intent-omni-channel-identity</name>
14+
<description>LiveIntent Omni-Channel Identity</description>
15+
16+
<dependencies>
17+
</dependencies>
18+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.config;
2+
3+
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.LiveIntentOmniChannelProperties;
4+
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.LiveIntentOmniChannelIdentityModule;
5+
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.hooks.LiveIntentOmniChannelIdentityProcessedAuctionRequestHook;
6+
import org.prebid.server.hooks.v1.Module;
7+
import org.prebid.server.json.JacksonMapper;
8+
import org.prebid.server.vertx.httpclient.HttpClient;
9+
import org.springframework.beans.factory.annotation.Value;
10+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
11+
import org.springframework.boot.context.properties.ConfigurationProperties;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
15+
import java.util.Collections;
16+
17+
@Configuration
18+
@ConditionalOnProperty(
19+
prefix = "hooks." + LiveIntentOmniChannelIdentityModule.CODE,
20+
name = "enabled",
21+
havingValue = "true")
22+
public class LiveIntentOmniChannelIdentityConfiguration {
23+
24+
@Bean
25+
@ConfigurationProperties(prefix = "hooks.modules." + LiveIntentOmniChannelIdentityModule.CODE)
26+
LiveIntentOmniChannelProperties properties() {
27+
return new LiveIntentOmniChannelProperties();
28+
}
29+
30+
@Bean
31+
Module liveIntentOmniChannelIdentityModule(LiveIntentOmniChannelProperties properties,
32+
JacksonMapper mapper,
33+
HttpClient httpClient,
34+
@Value("${logging.sampling-rate:0.01}") double logSamplingRate) {
35+
36+
final LiveIntentOmniChannelIdentityProcessedAuctionRequestHook hook =
37+
new LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(
38+
properties, mapper, httpClient, logSamplingRate);
39+
40+
return new LiveIntentOmniChannelIdentityModule(Collections.singleton(hook));
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model;
2+
3+
import com.iab.openrtb.request.Eid;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
8+
import java.util.List;
9+
10+
@Data
11+
@NoArgsConstructor
12+
@AllArgsConstructor(staticName = "of")
13+
public class IdResResponse {
14+
15+
List<Eid> eids;
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public final class LiveIntentOmniChannelProperties {
7+
8+
long requestTimeoutMs;
9+
10+
String identityResolutionEndpoint;
11+
12+
String authToken;
13+
14+
float treatmentRate;
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1;
2+
3+
import org.prebid.server.hooks.v1.Hook;
4+
import org.prebid.server.hooks.v1.InvocationContext;
5+
import org.prebid.server.hooks.v1.Module;
6+
7+
import java.util.Collection;
8+
9+
public record LiveIntentOmniChannelIdentityModule(
10+
Collection<? extends Hook<?, ? extends InvocationContext>> hooks) implements Module {
11+
12+
public static final String CODE = "liveintent-omni-channel-identity";
13+
14+
@Override
15+
public String code() {
16+
return CODE;
17+
}
18+
19+
@Override
20+
public Collection<? extends Hook<?, ? extends InvocationContext>> hooks() {
21+
return hooks;
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.hooks;
2+
3+
import com.iab.openrtb.request.BidRequest;
4+
import com.iab.openrtb.request.Eid;
5+
import com.iab.openrtb.request.User;
6+
import io.vertx.core.Future;
7+
import io.vertx.core.MultiMap;
8+
import org.apache.commons.collections4.ListUtils;
9+
import org.prebid.server.hooks.execution.v1.InvocationResultImpl;
10+
import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl;
11+
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.IdResResponse;
12+
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.LiveIntentOmniChannelProperties;
13+
import org.prebid.server.hooks.v1.InvocationAction;
14+
import org.prebid.server.hooks.v1.InvocationResult;
15+
import org.prebid.server.hooks.v1.InvocationStatus;
16+
import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;
17+
import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
18+
import org.prebid.server.hooks.v1.auction.ProcessedAuctionRequestHook;
19+
import org.prebid.server.json.JacksonMapper;
20+
import org.prebid.server.log.ConditionalLogger;
21+
import org.prebid.server.log.LoggerFactory;
22+
import org.prebid.server.util.HttpUtil;
23+
import org.prebid.server.util.ListUtil;
24+
import org.prebid.server.vertx.httpclient.HttpClient;
25+
import org.prebid.server.vertx.httpclient.model.HttpClientResponse;
26+
27+
import java.util.List;
28+
import java.util.Objects;
29+
import java.util.Optional;
30+
import java.util.concurrent.ThreadLocalRandom;
31+
32+
public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements ProcessedAuctionRequestHook {
33+
34+
private static final ConditionalLogger conditionalLogger = new ConditionalLogger(LoggerFactory.getLogger(
35+
LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.class));
36+
37+
private static final String CODE = "liveintent-omni-channel-identity-enrichment-hook";
38+
39+
private final LiveIntentOmniChannelProperties config;
40+
private final JacksonMapper mapper;
41+
private final HttpClient httpClient;
42+
private final double logSamplingRate;
43+
44+
public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(LiveIntentOmniChannelProperties config,
45+
JacksonMapper mapper,
46+
HttpClient httpClient,
47+
double logSamplingRate) {
48+
49+
this.config = Objects.requireNonNull(config);
50+
HttpUtil.validateUrlSyntax(config.getIdentityResolutionEndpoint());
51+
this.mapper = Objects.requireNonNull(mapper);
52+
this.httpClient = Objects.requireNonNull(httpClient);
53+
this.logSamplingRate = logSamplingRate;
54+
}
55+
56+
@Override
57+
public Future<InvocationResult<AuctionRequestPayload>> call(AuctionRequestPayload auctionRequestPayload,
58+
AuctionInvocationContext invocationContext) {
59+
60+
return config.getTreatmentRate() > ThreadLocalRandom.current().nextFloat()
61+
? requestIdentities(auctionRequestPayload.bidRequest())
62+
.<InvocationResult<AuctionRequestPayload>>map(this::update)
63+
.onFailure(throwable -> conditionalLogger.error(
64+
"Failed enrichment: %s".formatted(throwable.getMessage()), logSamplingRate))
65+
: noAction();
66+
}
67+
68+
private Future<IdResResponse> requestIdentities(BidRequest bidRequest) {
69+
return httpClient.post(
70+
config.getIdentityResolutionEndpoint(),
71+
headers(),
72+
mapper.encodeToString(bidRequest),
73+
config.getRequestTimeoutMs())
74+
.map(this::processResponse);
75+
}
76+
77+
private MultiMap headers() {
78+
return MultiMap.caseInsensitiveMultiMap()
79+
.add(HttpUtil.AUTHORIZATION_HEADER, "Bearer " + config.getAuthToken());
80+
}
81+
82+
private IdResResponse processResponse(HttpClientResponse response) {
83+
return mapper.decodeValue(response.getBody(), IdResResponse.class);
84+
}
85+
86+
private static Future<InvocationResult<AuctionRequestPayload>> noAction() {
87+
return Future.succeededFuture(InvocationResultImpl.<AuctionRequestPayload>builder()
88+
.status(InvocationStatus.success)
89+
.action(InvocationAction.no_action)
90+
.build());
91+
}
92+
93+
private InvocationResultImpl<AuctionRequestPayload> update(IdResResponse resolutionResult) {
94+
return InvocationResultImpl.<AuctionRequestPayload>builder()
95+
.status(InvocationStatus.success)
96+
.action(InvocationAction.update)
97+
.payloadUpdate(payload -> updatedPayload(payload, resolutionResult.getEids()))
98+
.build();
99+
}
100+
101+
private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayload, List<Eid> resolvedEids) {
102+
final List<Eid> eids = ListUtils.emptyIfNull(resolvedEids);
103+
final BidRequest bidRequest = requestPayload.bidRequest();
104+
final User updatedUser = Optional.ofNullable(bidRequest.getUser())
105+
.map(user -> user.toBuilder().eids(ListUtil.union(ListUtils.emptyIfNull(user.getEids()), eids)))
106+
.orElseGet(() -> User.builder().eids(eids))
107+
.build();
108+
109+
return AuctionRequestPayloadImpl.of(bidRequest.toBuilder().user(updatedUser).build());
110+
}
111+
112+
@Override
113+
public String code() {
114+
return CODE;
115+
}
116+
}

0 commit comments

Comments
 (0)