> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aiderx.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 추천 이벤트 추적

> 추천 결과의 노출·클릭·전환을 추적 토큰으로 A2에 기록하는 방법을 알아봅니다.

추천 로그는 고객 서비스에 표시한 A2 추천 결과의 노출·클릭·전환을 A2에 알리는 기록입니다. A2는 이 기록으로 추천 성과를 집계하고, 사용자별 추천 이력에 반영합니다.

## 개요

추천 요청이 성공하면 응답에 `tracking_token`이 들어 있습니다. 오류 응답에는 없습니다. `tracking_token`은 응답한 추천 결과를 가리키는 추적 토큰이며, 추천 이벤트는 이 값과 상품 ID·위치로 어떤 추천의 어느 상품에서 일어난 일인지 연결합니다.

| 이벤트 | `action` | 설명 |
| - | - | - |
| 응답 노출 | `imp` | 추천 결과를 받아 목록을 표시한 기록입니다. 응답 하나에 한 번 기록합니다. |
| 가시 노출 | `vimp` | 추천 상품이 사용자 화면에 실제로 보인 기록입니다. |
| 클릭 | `clk` | 사용자가 추천 상품을 클릭한 기록입니다. |
| 전환 | `conv` | 추천 상품을 클릭한 사용자가 구매 등 목표 행동을 완료한 기록입니다. |

추천 요청 방법은 [추천 요청](https://docs.aiderx.io/development/ko/serving-reco.md)을 참고하세요. 추천 로그 전송에는 액세스 토큰이 필요 없습니다.

> [!NOTE]
>
> `tracking_token`은 광고 응답의 `ext.ad_log`와 다른 값입니다. 이벤트에는 해당 추천 응답의 `tracking_token`을 수정하지 말고 그대로 사용하세요. 다른 응답의 값을 사용하면 의도한 추천과 연결되지 않습니다.

A2가 추천하지 않은 서비스 자체 목록의 노출은 추천 로그가 아니라 [사용자 행동 로그](https://docs.aiderx.io/development/ko/user-events.md)의 `item_impression`으로 보냅니다.

## 추천 응답에서 보관할 값

추천 응답을 받으면 목록을 표시하는 동안 `tracking_token`과 상품별 `catalog_id`·`position`을 함께 보관합니다. 전환을 측정한다면 사용자가 클릭한 상품의 값을 구매 완료 시점까지 전달해야 합니다.

```json 추천 응답 예시 theme={null}
{
  "version": "0.1.0",
  "items": [
    { "catalog_id": "catalog-123", "position": 0, "score": 0.87 },
    { "catalog_id": "catalog-234", "position": 1 }
  ],
  "tracking_token": "<tracking_token>",
  "ext": { "ad_log_tracking_mode": "on_event_tracker" }
}
```

| 필드 | 이벤트에 넣는 위치 |
| - | - |
| `tracking_token` | 모든 이벤트의 `tracking_token`에 그대로 넣습니다. |
| `items[].catalog_id` | 이벤트 대상 상품의 ID를 `event_items`에 넣습니다. |
| `items[].position` | 같은 상품의 위치를 `event_position`에 `event_items`와 같은 순서로 넣습니다. |
| `ext.ad_log_tracking_mode` | `imp`를 서비스에서 보낼지 판단합니다. [응답 노출 추적](#응답-노출-추적)을 참고하세요. |

서비스의 운영 규칙으로 순서를 바꾸거나 일부 상품을 빼고 표시해도 `event_position`에는 화면 위치가 아니라 응답의 `position`을 그대로 넣습니다. A2는 이 값으로 상품이 어떤 추천 방식에서 나왔는지 찾습니다. 응답에 없는 위치는 집계되지 않습니다.

## 응답 노출 추적

`imp`를 언제 기록할지는 추천 지면의 노출 추적 방식에 따릅니다. 응답의 `ext.ad_log_tracking_mode`로 확인합니다.

| 노출 추적 방식 | 기록 시점 | 고객 서비스에서 할 일 |
| - | - | - |
| **노출 이벤트 기준** (`on_event_tracker`, 기본값) | 서비스가 `imp`를 보낼 때 기록합니다. | 추천 목록을 표시할 때 `imp`를 한 번 보냅니다. |
| **추천 응답 기준** (`on_ad_response`) | 상품이 하나 이상 있는 추천 응답을 A2가 보낼 때 기록합니다. | 없습니다. `imp`를 보내지 마세요. |

`vimp`·`clk`·`conv`는 노출 추적 방식과 관계없이 서비스에서 보냅니다.

## 이벤트 보내기

모든 추천 이벤트는 [이벤트 기록 API](https://docs.aiderx.io/ko/0.5.0/decision/ad-log/record-user-log.md)로 보냅니다. `action`과 대상 상품만 다르고 요청 방식은 같습니다.

```bash theme={null}
curl -i -X POST "<A2_API_URL>/dmp/v0/log" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "reco_log",
    "action": "clk",
    "tracking_token": "<추천 응답의 tracking_token>",
    "event_items": ["catalog-234"],
    "event_position": [1]
  }'
```

HTTP `200`은 이벤트의 유효성이나 집계 완료를 보장하지 않습니다. 해석할 수 없는 토큰의 이벤트나 형식이 맞지 않는 항목은 집계에서 빠지므로 [요청 필드](#요청-필드)의 형식을 지켜 보내세요.

### 요청 필드

| 필드 | 필수 여부 | 설명 |
| - | - | - |
| `type` 문자열 | 필수 | `reco_log`로 보냅니다. |
| `action` 문자열 | 필수 | `imp`, `vimp`, `clk`, `conv` 중 하나입니다. |
| `tracking_token` 문자열 | 필수 | 추천 응답에서 받아 보관한 `tracking_token`입니다. |
| `event_items` 문자열 배열 | 필수 | 이벤트 대상 상품의 `catalog_id`입니다. 숫자 ID도 문자열로 보냅니다. |
| `event_position` 정수 배열 | 필수 | 이벤트 대상 상품의 응답 `position`입니다. `event_items`와 같은 순서로 넣습니다. |
| `seed_item_id` 문자열 | 선택 | 추천 요청에 `seed_item_id`를 보냈다면 같은 값을 넣습니다. 빈 문자열은 보내지 마세요. |
| `timestamp_in_ms` 정수 | 선택 | 이벤트가 발생한 Unix 시간입니다. **밀리초 단위**로 보냅니다. 생략하면 A2가 수집한 시각을 사용합니다. |

API Reference에서는 `event_items`와 `event_position`이 선택 필드입니다. 다만 `event_items`가 없으면 상품별 지표와 사용자 추천 이력에 반영되지 않고, `event_position`이 없으면 `vimp`·`clk`·`conv`가 추천 성과 지표에 집계되지 않으므로 항상 보내세요.

### 응답 노출 (`imp`)

노출 이벤트 기준일 때, 추천 목록을 표시하면 응답의 상품을 모두 넣어 한 번 보냅니다. 같은 응답의 목록을 다시 그리거나 페이지를 넘길 때는 다시 보내지 않습니다.

```javascript theme={null}
// 추천 목록을 표시한 뒤 응답마다 한 번 실행합니다.
if (reco.ext?.ad_log_tracking_mode !== "on_ad_response" && reco.items.length > 0) {
  sendRecoLog("imp", reco.tracking_token, reco.items);
}
```

아래 예시의 `sendRecoLog`는 이벤트 기록 API를 호출하는 함수입니다.

```javascript theme={null}
function sendRecoLog(action, trackingToken, items) {
  return fetch(`${A2_API_URL}/dmp/v0/log`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      type: "reco_log",
      action,
      tracking_token: trackingToken,
      event_items: items.map((item) => item.catalog_id),
      event_position: items.map((item) => item.position),
    }),
    keepalive: true,
  });
}
```

### 가시 노출 (`vimp`)

추천 상품이 화면에 실제로 보이면 보인 상품을 보냅니다. 같은 시점에 화면에 들어온 상품은 한 이벤트에 함께 넣고, 같은 응답에서 이미 보낸 상품은 다시 보내지 않습니다. 새 추천 응답의 상품은 위치가 같아도 따로 보냅니다. `vimp`를 보낼 때마다 노출 1회가 집계되므로 상품마다 따로 보내면 노출 수가 늘어납니다.

```javascript theme={null}
// 추천 응답마다 화면에 처음 보인 상품만 모아 보냅니다.
const sentByReco = new WeakMap();
function onRecoItemsVisible(reco, visibleItems) {
  if (!sentByReco.has(reco)) sentByReco.set(reco, new Set());
  const sent = sentByReco.get(reco);
  const items = visibleItems.filter((item) => !sent.has(item.position));
  if (items.length === 0) return;
  items.forEach((item) => sent.add(item.position));
  sendRecoLog("vimp", reco.tracking_token, items);
}
```

화면에 보였는지는 서비스에서 판단합니다. 웹에서는 `IntersectionObserver`처럼 요소가 화면 안에 들어왔는지 확인하는 방법을 사용할 수 있습니다.

### 클릭 (`clk`)

사용자가 추천 상품을 클릭하면 클릭한 상품 하나를 보냅니다. 브라우저에서는 클릭과 동시에 상품 페이지로 이동하므로 위 `sendRecoLog`처럼 `keepalive`를 사용합니다.

```javascript theme={null}
// 추천 상품 클릭 핸들러에서 실행합니다.
sendRecoLog("clk", reco.tracking_token, [item]);
```

서비스 서버에서 보내도 됩니다. 한 번의 클릭을 클라이언트와 서버에서 모두 보내지 마세요.

### 전환 (`conv`)

추천 상품을 클릭한 사용자가 구매를 완료하면, 클릭한 추천의 `tracking_token`과 구매한 상품의 `catalog_id`·`position`으로 `conv`를 보냅니다. 클릭 시점의 값을 장바구니와 결제 단계까지 전달해야 하므로 보통 구매를 확정하는 서비스 서버에서 보냅니다.

```bash theme={null}
curl -i -X POST "<A2_API_URL>/dmp/v0/log" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "reco_log",
    "action": "conv",
    "tracking_token": "<클릭한 추천의 tracking_token>",
    "event_items": ["catalog-234"],
    "event_position": [1],
    "timestamp_in_ms": 1760000000000
  }'
```

어떤 행동을 전환으로 볼지, 클릭 후 얼마 안에 일어난 구매까지 연결할지, 취소를 어떻게 처리할지는 고객사마다 다릅니다. 전환을 측정하려면 A2 담당자와 먼저 방식을 정하세요. 추천 로그는 구매 금액을 집계하지 않습니다.

[사용자 행동 로그](https://docs.aiderx.io/development/ko/user-events.md)의 구매 이벤트를 보내는 것만으로는 추천 전환이 집계되지 않습니다. 두 로그는 목적이 다르므로 각각 보냅니다.

## 사용자 추천 이력

추천 요청에 `user.id`를 보냈다면 토큰에 그 사용자가 담기고, `vimp`·`clk`·`conv`의 `event_items`가 사용자의 추천 이력에 반영됩니다. 추천 정책에 사용자 이력 조건이 있으면 A2는 이 이력으로 이미 본 상품을 제외하는 등 다음 추천을 조정합니다. `imp`는 이력에 반영되지 않습니다.

로그인하지 않은 사용자처럼 `user.id` 없이 받은 추천의 이벤트는 성과 지표에는 집계되지만 사용자 이력에는 반영되지 않습니다.

## 집계 확인

추천 성과 지표를 직접 조회하는 화면은 아직 제공되지 않습니다. 같은 광고 단위·기간의 서비스 기록을 정리해 A2 담당자와 대조합니다.

| 지표 | 대조할 고객 서비스 기록 |
| - | - |
| 응답 | 추천 응답 기준이면 상품이 있는 추천 응답 수, 노출 이벤트 기준이면 `imp` 전송 수입니다. |
| 노출 | `vimp` 전송 횟수입니다. |
| 가시 노출 | `vimp`로 보낸 상품 수입니다. |
| 클릭 | `clk`로 보낸 상품 수입니다. |
| 전환 | `conv`로 보낸 상품 수입니다. |

추천 성과는 A2 담당자가 구성한 추천 실험의 버킷별로 집계됩니다. 실험 없이 송출된 추천의 이벤트는 상품별 지표와 사용자 이력에만 반영됩니다.

개별 추천 로그의 수집 상태를 조회하는 화면이나 API는 제공되지 않습니다. 확인을 위해 같은 이벤트를 다시 보내지 마세요.

## 자주 발생하는 문제

> [accordion] 응답 수가 실제 표시보다 많습니다
>
> 추천 응답 기준이면 표시하지 않은 응답도 집계됩니다. 노출 이벤트 기준이면 같은 응답의 `imp`를 반복해서 보내지 않는지 확인합니다.

> [accordion] 클릭이나 가시 노출이 집계되지 않습니다
>
> `type`이 `reco_log`인지, `tracking_token`을 수정 없이 넣었는지, `event_position`에 응답의 `position`을 넣었는지 확인합니다.

> [accordion] 상품 위치별 성과가 실제와 다릅니다
>
> 서비스에서 재정렬한 화면 위치를 `event_position`에 넣지 않았는지, `event_items`와 `event_position`의 순서가 같은지 확인합니다.

> [accordion] 전환이 집계되지 않습니다
>
> 구매 시점에 클릭한 추천의 `tracking_token`과 `position`이 전달되는지 확인합니다. 다른 추천 응답의 토큰을 넣으면 연결되지 않습니다.

> [accordion] 타임아웃 후 다시 보내야 할지 알 수 없습니다
>
> 타임아웃만으로 처리 여부를 판단할 수 없습니다. 재전송하면 중복 집계될 수 있으므로 A2 담당자와 먼저 확인하세요.

문제가 계속되면 발생 시각, 광고 단위, 이벤트 종류, 민감정보를 제거한 요청과 HTTP 응답을 준비해 A2 담당자에게 문의하세요.

## 다음 단계

추천에 사용할 상품은 [상품 카탈로그](https://docs.aiderx.io/development/ko/catalog.md)에, 서비스 전체의 사용자 행동은 [사용자 행동 로그](https://docs.aiderx.io/development/ko/user-events.md)로 보냅니다.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.