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

# Bucket Integration

> Send sales data and journey events to Nemu through a cloud bucket (AWS S3 or Google Cloud Storage)

## What is the Bucket integration?

The **Bucket** integration is the integration method for enterprise operations that already centralize their data in a data warehouse (e.g. BigQuery). Instead of using a native integration or the API, the client periodically exports **sales** data and **journey events** (web and app) to a cloud bucket (**AWS S3** or **Google Cloud Storage**), and Nemu reads these files directly from there.

## Bucket access

<Tabs>
  <Tab title="AWS (S3)">
    Nemu creates a dedicated **IAM role** for the client and shares its ARN (e.g. `arn:aws:iam::<nemu-account>:role/<client>-s3-bucket-access`). The client then:

    1. Grants **read** access to this role on the bucket (bucket policy);
    2. Confirms the **bucket name**, **folder (prefix)** and **region** (e.g. `us-east-1`) with Nemu.

    <Tip>Alternatively, the client can create the role on their side and share access with Nemu.</Tip>
  </Tab>

  <Tab title="GCP (Cloud Storage)">
    Nemu provides the identity (**service account**) that needs read permission on the bucket. The client:

    1. Grants Nemu's service account the object reader role (`roles/storage.objectViewer`) on the bucket;
    2. Confirms the **bucket name**, **folder (prefix)** and **region/location** with Nemu.
  </Tab>
</Tabs>

## File format

The format used is **NDJSON** (one JSON object per line): it represents nested structures (such as the `products` list) without ambiguity and is natively exported by BigQuery.

<Warning>
  Sending **CSV** files is possible, but the file layout must be validated with
  Nemu before sending starts.
</Warning>

<Info>
  In NDJSON the value type matters: numbers go without quotes (e.g.
  `"campaign_id": 120210948573`, `"netValue": 699.99`) and text goes with
  quotes (e.g. `"content": "CARROSSEL_1080x1080"`). In CSV everything is text,
  and Nemu converts it based on the field type in the mapping. If the type is
  wrong (e.g. a number sent as text), the mapping flags it before importing.
</Info>

### Folder structure in the bucket

Inside the agreed prefix, organize files by journey and by date. The journey folders are `sales/` (sales) and `events/` (journey events):

```text theme={null}
acme-analytics-exports/
└── nemu/
    ├── sales/            ← sales
    │   └── 2026/03/14/   ← year / month / day
    │       └── orders.ndjson
    └── events/           ← journey events
        └── 2026/03/14/
            └── events.ndjson
```

<Info>
  Nemu runs the imports automatically, during off-peak hours. You don't need to
  schedule anything.
</Info>

## Sales schema

Each record represents an order (or one order per seller, in the case of marketplaces, see [Multi-seller](#multi-seller-marketplaces)). `products[]` is optional, but if sent it must have at least 1 item.

### Example

An order with two products:

```json Order with two products theme={null}
{
  "transactionId": "155480144",
  "name": "Adidas Dropset 4 Power Trainer Men's Sneakers",
  "netValue": 789.89,
  "grossValue": 789.89,
  "quantity": 1,
  "status": "paid",
  "paymentType": "credit_card",
  "customerName": "John Smith",
  "customerEmail": "customer@email.com",
  "date": "2026-06-28",
  "subscriptionId": "",
  "customerPhone": "+55 11 98888-7777",
  "orderCreatedAt": "2026-06-28 19:39:16",
  "priceCost": 470.00,
  "utm_source": "google",
  "utm_medium": "cpc",
  "utm_campaign": "PMAX_SHOES_JUN",
  "utm_content": "CARROSSEL-IMAGEM_1080x1080",
  "utm_term": "adidas dropset sneakers",
  "isFromApp": false,
  "coupons": ["CUPOM10"],
  "products": [
    {
      "productId": "FBA-83CC-256-42",
      "name": "Adidas Dropset 4 Power Trainer Men's Sneakers",
      "quantity": 1,
      "netValue": 699.99,
      "grossValue": 699.99,
      "priceCost": 420.00,
      "brand": "Adidas",
      "category": { "id": "15", "name": "Shoes" }
    },
    {
      "productId": "MEIA-77AB-40",
      "name": "Nike Sports Socks (Crew)",
      "quantity": 1,
      "netValue": 89.90,
      "grossValue": 89.90,
      "priceCost": 50.00,
      "brand": "Nike",
      "category": { "id": "22", "name": "Accessories" }
    }
  ]
}
```

### Order fields

| Field | Type | Required | Description |
| - | - | - | - |
| `transactionId` | string | ✅ | Unique order identifier (deduplication key) |
| `parentOrderId` | string | ➖ | Parent order id, groups child orders from different sellers (marketplace, see [Multi-seller](#multi-seller-marketplaces)) |
| `name` | string | ✅ | Order name / main product |
| `netValue` | number | ✅ | **Net** revenue of the order |
| `grossValue` | number | ✅ | **Gross** revenue of the order |
| `quantity` | number | ✅ | Total number of items |
| `status` | string | ✅ | Order status: `paid`, `waiting_payment`, `cancelled`, `chargeback`, `refunded` or `checkout_completed` |
| `paymentType` | string | ✅ | Payment method: `billet`, `credit_card`, `pix` or `others` |
| `customerName` | string | ✅ | Customer name |
| `customerEmail` | string | ✅ | Customer email. Accepts plain text or a hashed email value (e.g. SHA-256) |
| `date` | string | ✅ | Order date in `YYYY-MM-DD` format |
| `subscriptionId` | string | ➖ | Subscription identifier, when applicable |
| `customerPhone` | string | ➖ | Customer phone |
| `orderCreatedAt` | string | ➖ | Order creation time in `YYYY-MM-DD HH:mm:ss` format |
| `priceCost` | number | ➖ | Product cost (used to calculate margin) |
| `utm_source` | string | ➖ | UTM source of the sale |
| `utm_medium` | string | ➖ | UTM medium of the sale |
| `utm_campaign` | string | ➖ | UTM campaign of the sale |
| `utm_content` | string | ➖ | UTM content of the sale |
| `utm_term` | string | ➖ | UTM term of the sale, which carries Nemu's packed attribution data (see [Configuring UTMs](/en/bucket/configuring-utms)) |
| `isFromApp` | boolean | ➖ | `true` when the sale happened in the app |
| `coupons` | array | ➖ | List of discount coupon codes applied to the order (e.g. `["CUPOM10"]`) |
| `products` | array | ➖ | List of products in the order (at least 1 item when sent), see below |

### Product fields (`products[]`)

| Field | Type | Required | Description |
| - | - | - | - |
| `productId` | string | ✅ | Unique product identifier (SKU) |
| `name` | string | ✅ | Product name |
| `netValue` | number | ✅ | Net unit value |
| `grossValue` | number | ✅ | Gross unit value |
| `quantity` | integer | ✅ | Quantity (minimum `1`) |
| `priceCost` | number | ➖ | Product cost |
| `brand` | string | ➖ | Product brand (e.g. `Adidas`) |
| `category` | object | ➖ | Product category |
| `category.name` | string | ✅\* | Category name (\*required if `category` is sent) |
| `category.id` | string | ➖ | Category identifier |
| `category.imageUrl` | string | ➖ | Category image URL |

### Multi-seller (marketplaces)

For marketplace operations, each **child order (per seller)** is sent as its own record, linked to the "parent" order by the fields:

| Field | Type | Description |
| - | - | - |
| `transactionId` | string | Child order id, used as the dedup key (e.g. `155480144-1`) |
| `parentOrderId` | string | Parent order id that groups the sellers |

The parent order doesn't need to be sent or have its own status: the `status` comes in each child, and Nemu groups by `parentOrderId`, adding or subtracting each child according to its status (delivered counts, cancelled or returned is removed). These fields are optional: if you're not a marketplace, simply don't send `parentOrderId`.

In the example below, order `155480144` has items from two sellers and is sent as two child orders:

```json Child order 1 theme={null}
{
  "transactionId": "155480144-1",
  "parentOrderId": "155480144",
  "name": "Adidas Dropset 4 Sneakers",
  "netValue": 699.99,
  "grossValue": 699.99,
  "quantity": 1,
  "status": "paid",
  "paymentType": "credit_card",
  "customerName": "John Smith",
  "customerEmail": "customer@email.com",
  "date": "2026-06-28",
  "orderCreatedAt": "2026-06-28 19:39:16",
  "isFromApp": false,
  "products": [
    {
      "productId": "FBA-83CC-256-42",
      "name": "Adidas Dropset 4 Sneakers",
      "quantity": 1,
      "netValue": 699.99,
      "grossValue": 699.99,
      "priceCost": 420.0,
      "category": { "id": "15", "name": "Shoes" }
    }
  ]
}
```

```json Child order 2 theme={null}
{
  "transactionId": "155480144-2",
  "parentOrderId": "155480144",
  "name": "Nike Sports Socks (Crew)",
  "netValue": 49.9,
  "grossValue": 49.9,
  "quantity": 2,
  "status": "cancelled",
  "paymentType": "credit_card",
  "customerName": "John Smith",
  "customerEmail": "customer@email.com",
  "date": "2026-06-28",
  "orderCreatedAt": "2026-06-28 19:39:16",
  "isFromApp": false,
  "products": [
    {
      "productId": "MEIA-77AB-40",
      "name": "Nike Sports Socks",
      "quantity": 2,
      "netValue": 24.95,
      "grossValue": 24.95,
      "category": { "id": "22", "name": "Accessories" }
    }
  ]
}
```

<Info>
  In the **NDJSON** file, each of these records takes up **a single line**. The
  formatting above is just for readability.
</Info>

<Tip>
  Sub-orders of the same parent order can have **different statuses** (e.g. one
  seller `paid` and another `cancelled`). Send the actual status of each
  sub-order.
</Tip>

## Journey events schema

Journey events (web and app) follow the GA4/BigQuery export standard. Each record represents **one event**. `user_pseudo_id` is almost always present; `user_id` only when the user has logged in. `transaction_id` ties the purchase event to the sale.

### Example

A purchase event with full attribution:

```json purchase event theme={null}
{
  "event_date": "2026-07-11",
  "event_timestamp": "2026-07-11 16:53:56",
  "user_pseudo_id": "00de56a47fde1207286aa3325eb6714d",
  "user_id": "2c17315c-8e85-4b71-825a-02c63a0db79d",
  "session_id": "1783788836",
  "platform": "WEB",
  "event_name": "purchase",
  "transaction_id": "155480144",
  "source": "google",
  "medium": "cpc",
  "campaign_name": "RTG_VSA_SMART_VALOR_PANGLE",
  "campaign_id": 120210948573,
  "term": "nike sneakers",
  "content": "CARROSSEL-IMAGEM_1080x1080",
  "gclid": "Cj0KCQjw_8mHBhClARIsAP",
  "channel_group": "Paid Search",
  "item_id": "D74-58FP-008"
}
```

### Event fields

| Field | Type | Required | Description |
| - | - | - | - |
| `event_date` | string | ✅ | Event date in `YYYY-MM-DD` format (e.g. `2026-07-11`) |
| `event_timestamp` | string | ✅ | Exact time of the event, in `YYYY-MM-DD HH:mm:ss` format, used to order the journey |
| `user_pseudo_id` | string | ✅ | Anonymous device/browser id (e.g. `00de56a47fde1207286aa3325eb6714d`) |
| `user_id` | string | ➖ | Logged-in user id (only when a login happened) |
| `session_id` | string | ➖ | Session/visit id, which ties together events from the same browsing session (e.g. `1783788836`) |
| `platform` | enum | ✅ | Where the event happened: `WEB`, `ANDROID` or `IOS` |
| `event_name` | enum | ✅ | Event action (enables the funnel): `page_view`, `view_item`, `add_to_cart`, `begin_checkout`, `add_shipping_info`, `add_payment_info` or `purchase` |
| `transaction_id` | string | ➖ | Order id in the `purchase` event, the **bridge between journey and sale** (e.g. `155480144`) |
| `source` | string | ➖ | Traffic source (e.g. `google`, `tiktok`, `facebook`) |
| `medium` | string | ➖ | Traffic medium/channel (e.g. `cpc`, `organic`, `email`, `catalogo`) |
| `campaign_name` | string | ➖ | Marketing campaign name (e.g. `RTG_VSA_SMART_VALOR_PANGLE`) |
| `campaign_id` | number | ➖ | Numeric campaign id, more reliable than the name (e.g. `120210948573`) |
| `term` | string | ➖ | Search keyword/term or segment (e.g. `nike sneakers`) |
| `content` | string | ➖ | Variation/creative of the clicked ad (e.g. `CARROSSEL-IMAGEM_1080x1080`) |
| `gclid` | string | ➖ | Google Click Id, which matches the click with the Google Ads cost |
| `channel_group` | enum | ➖ | Channel grouping calculated by GA4: `Paid Social`, `Paid Search`, `Organic`, `Direct` or `Unassigned` |
| `item_id` | string | ➖ | Product id of the event, a possible bridge with the sale's `productId` (e.g. `D74-58FP-008`) |

<Info>
  The `transaction_id` of the `purchase` event must be the same `transactionId`
  sent in the sales file. It's what connects the browsing journey to the sale
  inside Nemu.
</Info>

## Configuring UTMs

In this integration, UTM configuration **does not follow Nemu's standard onboarding**. Since the `utm_source`, `utm_medium`, `utm_campaign` and `utm_content` fields are usually already in use by the client's GA4 and can't be changed, all of Nemu's attribution (source, campaign, ad set and creative) is **packed into a single `utm_term`**, which GA4 captures and passes on to Nemu.

See the step-by-step guide per platform (Meta, Google and TikTok) in [Configuring UTMs](/en/bucket/configuring-utms).

## Validation checklist

After the files start being sent, the client and Nemu validate the data loaded on the platform together:

1. **Payment methods:** all of the client's methods are correctly mapped to `billet`, `credit_card`, `pix` or `others`;
2. **Revenue:** **net** (`netValue`) and **gross** (`grossValue`) revenue values match the client's platform;
3. **Products:** name, quantity and values of each product are correct;
4. **Orders:** `transactionId` and revenue of each order match;
5. **UTMs:** `utm_term` is arriving in **Nemu's packed attribution format** (see [Configuring UTMs](/en/bucket/configuring-utms)).

<Warning>
  The integration is only considered complete after every checklist item has
  been validated by both sides. Revenue discrepancies or payment mapping issues
  must be fixed at the source (the client's export) before go-live.
</Warning>
