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

# Who Paid & How Much

> Associate traces with customers and orders to enable conversion analysis, per-customer cost accounting, and revenue attribution

## Overview

Customer and order attribution links your AI traces to the customers and transactions that triggered them. With it you can:

* **Measure conversions**: which conversations ultimately led to a payment
* **Per-customer cost accounting**: how much LLM spend each customer consumes
* **Fast troubleshooting**: customer complaint → find the corresponding trace → reproduce the issue
* **Optimize profit**: revenue (orders) minus cost (traces) = per-customer profit

## Property Specification

Use the following standard property names — the Anyway toolchain automatically recognizes and aggregates customer data.

| SDK Enum      | Property Key  | When to Set                               | Example Value      |
| ------------- | ------------- | ----------------------------------------- | ------------------ |
| `USER_ID`     | `user_id`     | Every request from an identified user     | `user-123`         |
| `CUSTOMER_ID` | `customer_id` | Every request from an identified customer | `CUS_a1b2c3d4e5f6` |
| `ORDER_ID`    | `order_id`    | In payment-related flows                  | `ORD_x7y8z9`       |
| `SESSION_ID`  | `session_id`  | Session tracking                          | `SES_a1b2c3d4e5f6` |

All properties are set via **association properties** — the SDK automatically propagates them to every span under the workflow.

### Usage Recommendations

* **`USER_ID`**: Set at the request entry point so it applies to the entire trace — use this for your application's internal user identifier
* **`CUSTOMER_ID`**: Set when you have an Anyway-format customer ID (e.g., from the Customers API)
* **`ORDER_ID`**: Set in payment-related workflows (e.g., creating a Payment Link, processing a payment)
* Use the `AssociationProperty` enum provided by the SDK to avoid hand-writing strings

### Customer ID Format

Customer IDs must use the Anyway format: `CUS_<12chars>`. Obtain them via the [Customers API](/api-reference/endpoint/customers) (`GET /v1/customers`).

## JavaScript SDK

### Per-Request Setup (Recommended)

The most common pattern: set the user ID in request-handling middleware so that every trace for that user is automatically attributed. If you also have an Anyway customer ID, include it alongside.

```typescript theme={null}
import { withWorkflow, AssociationProperty } from "@anyway-sh/node-server-sdk";

app.post("/chat", async (req, res) => {
  const userId = req.auth.userId; // From your auth middleware
  const customerId = req.auth.customerId; // Optional: Anyway customer ID

  const result = await withWorkflow(
    {
      name: "chat",
      associationProperties: {
        [AssociationProperty.USER_ID]: userId,
        [AssociationProperty.CUSTOMER_ID]: customerId,
      },
    },
    async () => {
      // All LLM spans inside automatically inherit these properties
      return await agent.chat(req.body.message);
    },
  );

  res.json(result);
});
```

### Class Decorator Approach

```typescript theme={null}
import { workflow, task, AssociationProperty } from "@anyway-sh/node-server-sdk";

class ChatService {
  private customerId: string;

  constructor(customerId: string) {
    this.customerId = customerId;
  }

  @workflow((self) => ({
    name: "chat",
    associationProperties: {
      [AssociationProperty.CUSTOMER_ID]: (self as ChatService).customerId,
    },
  }))
  async chat(message: string) {
    return await this.classify(message);
  }

  @task({ name: "classify" })
  async classify(message: string) {
    // LLM call — automatically inherits customer attribution properties
  }
}
```

### Setting Order ID / Product ID in Payment Flows

In payment-related workflows, pass order and product information via association properties:

```typescript theme={null}
import { withWorkflow, AssociationProperty } from "@anyway-sh/node-server-sdk";

async function handlePayment(customerId: string, productId: string) {
  return withWorkflow(
    {
      name: "process_payment",
      associationProperties: {
        [AssociationProperty.CUSTOMER_ID]: customerId,
      },
    },
    async () => {
      const link = await paymentApi.createLink(productId);

      // Once we have the orderId, set it in the inner workflow/task
      return withWorkflow(
        {
          name: "complete_order",
          associationProperties: {
            [AssociationProperty.CUSTOMER_ID]: customerId,
            [AssociationProperty.ORDER_ID]: link.orderId,
          },
        },
        async () => {
          return await fulfillOrder(link.orderId);
        },
      );
    },
  );
}
```

## Python SDK

### Per-Request Setup (Recommended)

```python theme={null}
from anyway.sdk import Traceloop
from anyway.sdk.decorators import workflow, task
from anyway.sdk.associations import AssociationProperty

def handle_chat(user_id: str, customer_id: str, message: str):
    Traceloop.set_association_properties({
        AssociationProperty.USER_ID: user_id,
        AssociationProperty.CUSTOMER_ID: customer_id,
    })
    return chat_workflow(message)

@workflow(name="chat")
def chat_workflow(message: str):
    return agent.chat(message)
```

### Dynamically Setting Customer ID (From Request Context)

```python theme={null}
from anyway.sdk import Traceloop
from anyway.sdk.decorators import workflow, task
from anyway.sdk.associations import AssociationProperty

def chat_handler(request):
    user_id = request.auth["user_id"]
    customer_id = request.auth["customer_id"]

    # Set for all spans in the current context
    Traceloop.set_association_properties({
        AssociationProperty.USER_ID: user_id,
        AssociationProperty.CUSTOMER_ID: customer_id,
    })

    return process_message(request.body["message"])

@workflow(name="process_message")
def process_message(message: str):
    return call_llm(message)
```

### Setting Order ID / Product ID in Payment Flows

```python theme={null}
from anyway.sdk import Traceloop
from anyway.sdk.decorators import workflow, task
from anyway.sdk.associations import AssociationProperty

def process_payment(customer_id: str, product_id: str):
    Traceloop.set_association_properties({
        AssociationProperty.CUSTOMER_ID: customer_id,
    })
    link = payment_api.create_link(product_id)
    return complete_order(customer_id, link.order_id)

def complete_order(customer_id: str, order_id: str):
    Traceloop.set_association_properties({
        AssociationProperty.CUSTOMER_ID: customer_id,
        AssociationProperty.ORDER_ID: order_id,
    })
    return fulfill_order_workflow(order_id)

@workflow(name="fulfill_order")
def fulfill_order_workflow(order_id: str):
    return fulfill_order(order_id)
```

## Common Scenarios

### Anonymous User → Registered User

When a user transitions from anonymous to registered/paying:

```typescript theme={null}
// Phase 1: Anonymous session
await withWorkflow(
  {
    name: "chat",
    associationProperties: {
      [AssociationProperty.CUSTOMER_ID]: `anon_${sessionId}`, // Temporary ID
    },
  },
  async () => {
    // Interactions before registration
  },
);

// Phase 2: After registration/payment — use the real customer ID
await withWorkflow(
  {
    name: "chat",
    associationProperties: {
      [AssociationProperty.CUSTOMER_ID]: "CUS_a1b2c3d4e5f6", // Real ID from order system
    },
  },
  async () => {
    // Interactions after registration
  },
);
```

<Tip>
  To link anonymous and identified traces, store an `anon_<sessionId> → CUS_xxx` mapping in your application layer. Query both IDs when analyzing the full customer journey.
</Tip>

## Next Steps

<CardGroup cols={2}>
  <Card title="Cost Tracking" icon="dollar-sign" href="/features/cost-tracking">
    Configure model pricing for accurate cost calculations
  </Card>

  <Card title="Tracing" icon="route" href="/features/tracing">
    Learn about Workflows, Tasks, and Span Attributes
  </Card>
</CardGroup>
