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

# Map Partner Events to Business Events

> Add business events, input event rules, and event data to a partner integration that receives partner events, then verify what arrives.

Stage three of [Sending Partner Events to Extole](/technical/building-partner-integrations/integration-types/partner-events-to-extole).

<Info>
  **You probably do not need this page.** It covers building a partner platform into a reusable integration — a connector meant to be installed and maintained across accounts. To get your own purchases, shipments, or account activity into Extole, [send them to the Events API](/technical/building-partner-integrations/integration-types/partner-events-send-from-platform) or use one of the paths in [Integrating with Extole](/technical/integration-overview/integrating-with-extole); if Extole already maintains a source for the platform, install it from the My Extole [Integrations page](https://my.extole.com/integrations).

  When building one **is** the job, run the build through the [Extole MCP server](/technical/extole-ai-tools/extole-mcp-server/index) in an agentic coding tool rather than issuing these calls by hand — see [Build with Extole AI Tools](/technical/building-partner-integrations/integration-lifecycle/management-api-integration#build-with-extole-ai-tools).
</Info>

# Overview

This is the core of a Partner to Extole integration: each event arriving from the partner platform becomes a canonical Extole business event.

```text theme={null}
root
└── partner                     integration-v10.x
    ├── businessEvents          MULTI_SOCKET → business-event-v10.0
    │   ├── converted           template_transacted_business_event
    │   │   ├── triggerRules
    │   │   │   └── input_event
    │   │   └── data
    │   │       ├── partner_conversion_id
    │   │       ├── cart_value
    │   │       └── other mapped fields
    │   ├── shipped             template_tracked_business_event
    │   └── canceled            template_tracked_business_event
    └── views                   MULTI_SOCKET → view-v10.0
        ├── configuration       config-view-v10.0
        ├── report-runner-view  report-runner-view-v10.0
        └── event-streams       event-stream-view-v10.0
```

Never rename an Extole business event to match a partner wire event: the partner name belongs in an `input_event` trigger rule, the canonical name on the business-event component.

The three views are the same for every category — see [Integration Categories](/technical/building-partner-integrations/integration-types/integration-categories). Chart and stream this integration's canonical business events, and build the sender against [Send Events from the Partner Platform](/technical/building-partner-integrations/integration-types/partner-events-send-from-platform).

This page assumes the event arrives with a name in `event_name` and its fields readable in `data`. When the partner's own webhook defines the payload instead, a prehandler renames and flattens it first — see [Normalize Partner Events with a Prehandler](/technical/building-partner-integrations/integration-types/partner-events-prehandlers).

## Add Reusable Business Events

Choose the template by meaning:

* `template_transacted_business_event` — the event carries transaction value or represents the program outcome.
* `template_tracked_business_event` — a lifecycle milestone without transaction value.

Duplicate the source into the `businessEvents` socket:

```bash theme={null}
curl --request POST \
  "$EXTOLE_API_HOST/v1/components/$BUSINESS_EVENT_TEMPLATE_ID/duplicate" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "target_campaign_id": "'"$CAMPAIGN_ID"'",
    "target_component_absolute_name": "/example",
    "target_setting_name": "businessEvents",
    "component_name": "converted",
    "component_display_name": "Converted",
    "description": "Records a completed partner transaction.",
    "variables": [
      {
        "name": "eventName",
        "type": "STRING",
        "values": {
          "default": "converted"
        }
      },
      {
        "name": "aliases",
        "type": "STRING_LIST",
        "values": {
          "default": [
            "conversion",
            "customer",
            "outcome",
            "transacted"
          ]
        }
      },
      {
        "name": "singularNounName",
        "type": "STRING",
        "values": {
          "default": "Conversion"
        }
      },
      {
        "name": "adminUIIcon",
        "type": "STRING",
        "values": {
          "default": "fa-regular fa-shopping-cart"
        }
      },
      {
        "name": "pluralNounName",
        "type": "STRING",
        "values": {
          "default": "Conversions"
        }
      },
      {
        "name": "rateName",
        "type": "STRING",
        "values": {
          "default": "Conversion Rate"
        }
      },
      {
        "name": "sequence",
        "type": "STRING",
        "values": {
          "default": "103.2"
        }
      },
      {
        "name": "dataCapturePolicy",
        "type": "ENUM",
        "values": {
          "default": "NO_ADDITIONAL_DATA"
        }
      }
    ]
  }'
```

### Name Each Event for Its Outcome

Use the canonical v10 name for the business outcome: `converted`, `shipped`, `canceled`, `returned`, `account_opened`, `application_approved`. Do not use a transport-specific name such as `partner_order_created`, and do not create duplicate legacy controllers alongside the reusable component.

Create one business event per distinct outcome. Two wire events share one only when nobody would need them apart in a report, a rule, or a clawback: a refunded charge is `returned`, not `canceled`.

### Set the Reporting and Ordering Variables

Set these on every duplicate:

| Variable                                         | Set it to                                                                                                      |
| :----------------------------------------------- | :------------------------------------------------------------------------------------------------------------- |
| `sequence`                                       | A value sorting the events in lifecycle order, outcome first. The numbers matter only relative to one another. |
| `singularNounName`, `pluralNounName`, `rateName` | The nouns and rate name reports and the admin funnel display.                                                  |
| `adminUIIcon`                                    | An icon that reads as this event's outcome. No two events in one integration share an icon.                    |

Two events from the same template report under identical labels until you override these: a tracked template calls everything "Tracked Events" with a "Tracked Event Rate", and `singularNounName` defaults to an expression echoing the display name.

### Set the Alias Set the Platform Already Uses

Set `aliases` to the set the platform already uses, read from an existing program's business event of the same name. They are additional names the business event matches, and platform consumers subscribe to them: an extension listening for `outcome` sees a `converted` event only because `converted` carries that alias.

| Canonical event       | Aliases                                           |
| :-------------------- | :------------------------------------------------ |
| `converted`           | `conversion`, `customer`, `outcome`, `transacted` |
| `account_qualified`   | `customer`, `outcome`                             |
| `account_opened`      | `customer`                                        |
| `shipped`, `canceled` | none                                              |

Do not invent partner-flavored aliases or clear a set the template provides. One alias must never appear on two business events in one campaign, which makes the match ambiguous.

### Preserve the Journey and Role Defaults

Inspect the duplicated component's evaluated `journeyName` and `roleName`. Both depend on the surrounding role and journey hierarchy, so keep the template's defaults unless the contract requires otherwise, and record the published values during verification.

## Configure Input Event Rules

Duplicate `input_event` from the `rules` program into each `triggerRules` socket:

```bash theme={null}
curl --request POST \
  "$EXTOLE_API_HOST/v1/components/$INPUT_EVENT_TEMPLATE_ID/duplicate" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "target_campaign_id": "'"$CAMPAIGN_ID"'",
    "target_component_absolute_name": "/example/converted",
    "target_setting_name": "triggerRules",
    "variables": [
      {
        "name": "triggerEventNames",
        "type": "STRING_LIST",
        "values": {
          "default": [
            "partner_order_created"
          ]
        }
      }
    ]
  }'
```

Keep the duplicated rule under its source name `input_event`, and put the partner event name in `triggerEventNames`. When a prehandler renames the event, list the name the prehandler produces — an integration-owned name such as `example_order_completed`, never the canonical name the business event carries. A rule still listening for the wire name never matches, and reads as correct in the component tree. Add a legacy alias only when a real sender still emits it.

## Configure Event Data

Create the data components in the same run as their business events. A business event with an empty `data` socket produces a step with no transaction identifier, no person key, and no value, so nothing downstream can deduplicate, attribute, reward, or report on it.

Take the field names from the partner's documented payload — a partner page in this documentation set, the partner's developer documentation, or a captured sample. One unknown field does not defer the rest.

Define the mapping first:

| Extole field            | Partner field          | Required                                      | Key type                   | Persist type |
| :---------------------- | :--------------------- | :-------------------------------------------- | :------------------------- | :----------- |
| `partner_conversion_id` | Transaction identifier | Yes for transaction events                    | `UNIQUE_PARTNER_EVENT_KEY` | `STEP`       |
| `partner_user_id`       | Customer identifier    | Recommended                                   | `PARTNER_PROFILE_KEY`      | `STEP`       |
| `cart_value`            | Transaction amount     | Recommended for revenue events                | `VALUE`                    | `STEP`       |
| `email`                 | Email address          | Required when no other identity is sufficient | `NONE`                     | `STEP`       |
| `first_name`            | First name             | Optional                                      | `NONE`                     | `STEP`       |
| `last_name`             | Last name              | Optional                                      | `NONE`                     | `STEP`       |
| `coupon_code`           | Applied coupon         | Optional                                      | `NONE`                     | `STEP`       |

Duplicate one `business_event_data` component into the `data` socket for each field:

```bash theme={null}
curl --request POST \
  "$EXTOLE_API_HOST/v1/components/$BUSINESS_EVENT_DATA_TEMPLATE_ID/duplicate" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "target_campaign_id": "'"$CAMPAIGN_ID"'",
    "target_component_absolute_name": "/example/converted",
    "target_setting_name": "data",
    "component_name": "partner_conversion_id",
    "variables": [
      {
        "name": "name",
        "type": "STRING",
        "values": {
          "default": "partner_conversion_id"
        }
      },
      {
        "name": "valueExpression",
        "type": "STRING",
        "values": {
          "default": "javascript@runtime:context.getCauseEvent().getData()[\"order_id\"]"
        }
      },
      {
        "name": "scope",
        "type": "ENUM",
        "values": {
          "default": "PRIVATE"
        }
      },
      {
        "name": "persistTypes",
        "type": "ENUM_LIST",
        "values": {
          "default": [
            "STEP"
          ]
        }
      },
      {
        "name": "keyType",
        "type": "ENUM",
        "values": {
          "default": "UNIQUE_PARTNER_EVENT_KEY"
        }
      }
    ]
  }'
```

Set `valueExpression` explicitly when the partner field and Extole field have distinct names — do not rely on the name-derived default — and verify it against a real sanitized partner payload. Use `NO_ADDITIONAL_DATA` when the event must store only declared fields, and capture no more than identity, deduplication, attribution, rules, and reporting need.

<Warning>
  Quote the key with escaped double quotes, as above. A single-quoted `--data` payload cannot contain single quotes: the shell closes the string at the first one, so `getData()['order_id']` reaches the API as `getData()[order_id]`, which reads an undefined variable instead of the partner field and silently captures nothing.
</Warning>

Give every business event its own data components; a field captured on one is not visible on another:

* **Identity**, on every event: the partner customer identifier as `PARTNER_PROFILE_KEY` when the partner sends a stable one, `email` when it is the only identity available.
* **The transaction identifier as `UNIQUE_PARTNER_EVENT_KEY`**, on transaction events, so an event tied to a partner-side record can deduplicate. A standalone event such as `account_opened` has none.
* **The amount as `VALUE`**, on revenue events.

Lifecycle events such as `shipped` and `canceled` arrive independently, so repeat the identity set on each: they must resolve to the same person and, through the transaction identifier, the same original transaction.

## How to Test

Send a synchronous test event through `POST /v6/events` with an event-submission token, not the one that created the campaign. Put the current program label in `data` to target this campaign; it is a testing convenience and does not belong in the partner's payload.

```bash theme={null}
curl --request POST "$EXTOLE_API_HOST/v6/events" \
  --header "Authorization: Bearer $EVENTS_API_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "event_name": "partner_order_created",
    "data": {
      "email": "test@example.com",
      "order_id": "test-order-001",
      "total": 42.50,
      "customer_id": "test-customer-001",
      "labels": "'"$PROGRAM_LABEL"'"
    }
  }'
```

Use the returned `person_id` to list the resulting events:

```bash theme={null}
curl --get "$EXTOLE_API_HOST/v5/persons/$PERSON_ID/steps" \
  --header "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
  --data-urlencode "campaign_ids=$CAMPAIGN_ID" \
  --data-urlencode "names=converted"
```

Confirm the canonical event name and every mapped value, using a new unique identifier per repeat unless duplicate handling is under test. Cover:

* Every preferred partner input event.
* Every supported legacy alias.
* Missing required identity.
* A repeated unique partner event identifier.
* Invalid amount and status values.
* Partner retry behavior.
* Campaign targeting with the current program label.
* Configuration view contents and status.
