Skip to main content

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 EnumProperty KeyWhen to SetExample Value
USER_IDuser_idEvery request from an identified useruser-123
CUSTOMER_IDcustomer_idEvery request from an identified customerCUS_a1b2c3d4e5f6
ORDER_IDorder_idIn payment-related flowsORD_x7y8z9
SESSION_IDsession_idSession trackingSES_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 (GET /v1/customers).

JavaScript SDK

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.
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

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:
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

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)

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

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:
// 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
  },
);
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.

Next Steps

Cost Tracking

Configure model pricing for accurate cost calculations

Tracing

Learn about Workflows, Tasks, and Span Attributes