> This page is for version v1.
> For other versions, use one of these documentation indexes:
> - v2 (default): https://docs.paid.ai/v-2/llms.txt
> - v1: https://docs.paid.ai/v-1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paid.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paid.ai/_mcp/server.

# Integrating Signals with Cost Tracking

> Learn how to send signals and track AI usage costs using the Paid SDK.

## Overview

This guide shows you how to integrate **cost tracking** with **Signals** in your AI product workflow. By linking these two together, you can:

* Automatically attribute AI provider costs (OpenAI, Anthropic, etc.) to specific business events
* Track the true cost of achieving business outcomes (e.g., booking a meeting, sending an email, processing an image or a document)
* Generate accurate invoices that reflect both usage-based pricing and actual AI costs

## Prerequisites

Before you begin, make sure you have:

* Created an API key in the Paid dashboard
* Installed the Paid SDK for your language
* Configured at least one Product with Signals
* Set up your customer and order records

> **Note**: This feature is currently available in **Python** and **Node.js** SDKs.

## How It Works

In a typical AI product workflow, your event processing logic might look like this:

```
1. Receive event (e.g., "prospect replied to email")
2. Process event → make AI calls to generate response
3. Send response
4. Send signal to Paid
```

To link AI costs with signals, you need to:

1. **Process your AI calls** as usual with your AI provider
2. **Send signals to Paid** using the Paid SDK

This allows Paid to track your business outcomes and attribute costs accordingly.

## Automatic Cost Tracking

The Paid SDK can automatically instrument your AI provider calls so that costs are tracked without any manual bookkeeping. Call `initialize_tracing` and `autoinstrument` once at startup, then wrap your business logic in a trace. Supported providers include **OpenAI**, **Anthropic**, **Gemini**, **Bedrock**, **LangChain**, **OpenAI Agents**, and **Instructor**.

#### Python

```python
from paid.tracing import paid_autoinstrument, paid_tracing, initialize_tracing
from openai import OpenAI

initialize_tracing()
paid_autoinstrument(libraries=["openai"])

openai_client = OpenAI(api_key="<OPENAI_API_KEY>")

@paid_tracing("your_external_customer_id", external_product_id="your_external_product_id")
def chat_with_gpt():
    response = openai_client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": "Hello!"}]
    )
    return response

chat_with_gpt()
```

#### Node.js

```typescript
import OpenAI from "openai";
import { trace, initializeTracing } from "@paid-ai/paid-node/tracing";
import { paidAutoInstrument } from "@paid-ai/paid-node/tracing/auto";

initializeTracing(process.env.PAID_API_TOKEN!);
await paidAutoInstrument(["openai"]);

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
});

async function chatWithGpt() {
  const result = await trace(
    { externalCustomerId: "your_external_customer_id", externalProductId: "your_external_product_id" },
    async () => {
      const completion = await openai.chat.completions.create({
        model: "gpt-4",
        messages: [{ role: "user", content: "Hello!" }],
      });
      return completion.choices[0]?.message?.content ?? "";
    },
  );
  return result;
}

await chatWithGpt();
```

#### Next.js

```typescript
import { NextResponse } from "next/server";
import OpenAI from "openai";
import { trace, initializeTracing } from "@paid-ai/paid-node/tracing";
import { paidAutoInstrumentModules } from "@paid-ai/paid-node/tracing/auto";

initializeTracing(process.env.PAID_API_TOKEN!);
await paidAutoInstrumentModules({ openai: OpenAI });

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });

export async function POST(request: Request) {
  const { prompt, externalCustomerId } = await request.json();
  const result = await trace(
    { externalCustomerId, externalProductId: "sample-nextjs-app" },
    async () => {
      const completion = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [{ role: "user", content: prompt }],
      });
      return completion.choices[0]?.message?.content ?? "";
    },
  );
  return NextResponse.json({ success: true, response: result });
}
```

#### AI SDK

```typescript
import { NextResponse } from "next/server";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { initializeTracing, trace, getPaidTracer } from "@paid-ai/paid-node/tracing";

initializeTracing(process.env.PAID_API_TOKEN!);

export async function POST(request: Request) {
  try {
    const { prompt, externalCustomerId } = await request.json();

    if (!prompt) {
      return NextResponse.json({ error: "prompt is required" }, { status: 400 });
    }
    if (!externalCustomerId) {
      return NextResponse.json(
        { error: "externalCustomerId is required" },
        { status: 400 },
      );
    }

    const result = await trace(
      { externalCustomerId, externalProductId: "sample-aisdk-app" },
      async () => {
        const { text } = await generateText({
          model: openai("gpt-4o-mini"),
          prompt,
          experimental_telemetry: { isEnabled: true, tracer: getPaidTracer() },
        });

        return text;
      },
    );

    return NextResponse.json({ success: true, response: result });
  } catch (error) {
    console.error("Failed:", error);
    return NextResponse.json(
      {
        error: "Failed to process request",
        message: error instanceof Error ? error.message : String(error),
      },
      { status: 500 },
    );
  }
}
```

## Basic Integration Pattern

Here's the recommended pattern for integrating cost tracking with signals using the SDK:

#### Python

```python
from paid import Paid
from openai import OpenAI

# Initialize clients
paid_client = Paid(token="YOUR_PAID_API_KEY")
openai_client = OpenAI(api_key="<OPENAI_API_KEY>")

def process_event(event, customer_id: str, product_id: str):
    """
    Process an event and send signal via SDK
    """
    # Step 1: Make AI calls
    response = openai_client.chat.completions.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "You are an AI SDR assistant"},
            {"role": "user", "content": event.content}
        ]
    )

    # Step 2: Your business logic
    send_response(response.choices[0].message.content)

    # Step 3: Send signal via SDK
    paid_client.signals.create_signals(
        signals=[{
            "event_name": "email_sent",
            "customer": {"external_customer_id": customer_id},
            "attribution": {"external_product_id": product_id},
            "data": {"response_length": len(response.choices[0].message.content)}
        }]
    )

# Call your event processor
process_event(incoming_event, "customer_123", "agent_123")
```

#### Node.js

```typescript
import { PaidClient } from "@paid-ai/paid-node";
import OpenAI from "openai";

// Initialize clients
const paidClient = new PaidClient({ token: "YOUR_PAID_API_KEY" });
const openaiClient = new OpenAI({ apiKey: "<OPENAI_API_KEY>" });

async function processEvent(event: Event, customerId: string, productId: string) {
    /**
     * Process an event and send signal via SDK
     */
    // Step 1: Make AI calls
    const response = await openaiClient.chat.completions.create({
        model: "gpt-4",
        messages: [
            { role: "system", content: "You are an AI SDR assistant" },
            { role: "user", content: event.content }
        ]
    });

    // Step 2: Your business logic
    await sendResponse(response.choices[0].message.content);

    // Step 3: Send signal via SDK
    await paidClient.signals.createSignals({
        signals: [{
            eventName: "email_sent",
            customer: { externalCustomerId: customerId },
            attribution: { externalProductId: productId },
            data: { responseLength: response.choices[0].message.content.length }
        }]
    });
}

// Call your event processor
await processEvent(incomingEvent, "customer_123", "agent_123");
```

## Real-World Example: AI SDR Product

Let's walk through a complete example of an AI SDR product that processes email replies and sends signals via the SDK.

#### Python

```python
from paid import Paid
from openai import OpenAI

# Initialize clients
paid_client = Paid(token="YOUR_PAID_API_KEY")
openai_client = OpenAI(api_key="<OPENAI_API_KEY>")

def handle_email_reply(email, customer_id: str):
    """
    Handle incoming email reply from prospect
    """
    # AI call #1: Analyze sentiment
    sentiment_response = openai_client.chat.completions.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "Analyze email sentiment"},
            {"role": "user", "content": email.body}
        ]
    )

    sentiment = sentiment_response.choices[0].message.content

    # AI call #2: Generate response
    reply_response = openai_client.chat.completions.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": f"Generate reply. Sentiment: {sentiment}"},
            {"role": "user", "content": email.body}
        ]
    )

    reply_text = reply_response.choices[0].message.content

    # Business logic: Send the email
    send_email(
        to=email.sender,
        subject=f"Re: {email.subject}",
        body=reply_text
    )

    # Send signal via SDK
    paid_client.signals.create_signals(
        signals=[{
            "event_name": "email_reply_processed",
            "customer": {"external_customer_id": customer_id},
            "attribution": {"external_product_id": "ai_sdr_product"},
            "data": {
                "sentiment": sentiment,
                "reply_length": len(reply_text)
            },
            "idempotency_key": f"email_reply_{email.id}"
        }]
    )

# Process incoming email
handle_email_reply(incoming_email, "acme_account_123")
```

#### Node.js

```typescript
import { PaidClient } from "@paid-ai/paid-node";
import OpenAI from "openai";

// Initialize clients
const paidClient = new PaidClient({ token: "YOUR_PAID_API_KEY" });
const openaiClient = new OpenAI({ apiKey: "<OPENAI_API_KEY>" });

async function handleEmailReply(email: Email, customerId: string) {
    /**
     * Handle incoming email reply from prospect
     */
    // AI call #1: Analyze sentiment
    const sentimentResponse = await openaiClient.chat.completions.create({
        model: "gpt-4",
        messages: [
            { role: "system", content: "Analyze email sentiment" },
            { role: "user", content: email.body }
        ]
    });

    const sentiment = sentimentResponse.choices[0].message.content;

    // AI call #2: Generate response
    const replyResponse = await openaiClient.chat.completions.create({
        model: "gpt-4",
        messages: [
            { role: "system", content: `Generate reply. Sentiment: ${sentiment}` },
            { role: "user", content: email.body }
        ]
    });

    const replyText = replyResponse.choices[0].message.content;

    // Business logic: Send the email
    await sendEmail({
        to: email.sender,
        subject: `Re: ${email.subject}`,
        body: replyText
    });

    // Send signal via SDK
    await paidClient.signals.createSignals({
        signals: [{
            eventName: "email_reply_processed",
            customer: { externalCustomerId: customerId },
            attribution: { externalProductId: "ai_sdr_product" },
            data: {
                sentiment: sentiment,
                replyLength: replyText.length
            },
            idempotencyKey: `email_reply_${email.id}`
        }]
    });
}

// Process incoming email
await handleEmailReply(incomingEmail, "acme_account_123");
```

## Event Loop Integration

Most AI products run in an event loop, processing events continuously. Here's how to integrate signal tracking in that pattern:

#### Python

```python
from paid import Paid
from openai import OpenAI

# Initialize clients once
paid_client = Paid(token="YOUR_PAID_API_KEY")
openai_client = OpenAI(api_key="<OPENAI_API_KEY>")

def send_signal(event_name: str, customer_id: str, product_id: str, data: dict = None):
    """Helper function to send signals"""
    signal = {
        "event_name": event_name,
        "customer": {"external_customer_id": customer_id},
        "attribution": {"external_product_id": product_id}
    }
    if data:
        signal["data"] = data

    paid_client.signals.create_signals(signals=[signal])

def process_event(event, customer_id: str, product_id: str):
    """Process a single agent event"""

    # Make AI calls as needed
    if event.type == "email_received":
        response = openai_client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": event.content}]
        )
        handle_email_response(response)

        # Send signal
        send_signal("email_processed", customer_id, product_id)

    elif event.type == "call_completed":
        # Different AI calls for call processing
        summary = openai_client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": f"Summarize: {event.transcript}"}]
        )

        send_signal("call_summarized", customer_id, product_id)

# Event loop
while True:
    event = get_next_event()  # Your event queue
    if event:
        process_event(event, "customer_123", "agent_123")
```

#### Node.js

```typescript
import { PaidClient } from "@paid-ai/paid-node";
import OpenAI from "openai";

// Initialize clients once
const paidClient = new PaidClient({ token: "YOUR_PAID_API_KEY" });
const openaiClient = new OpenAI({ apiKey: "<OPENAI_API_KEY>" });

async function sendSignal(eventName: string, customerId: string, productId: string, data?: any) {
    const signal: any = {
        eventName,
        customer: { externalCustomerId: customerId },
        attribution: { externalProductId: productId }
    };
    if (data) signal.data = data;

    await paidClient.signals.createSignals({ signals: [signal] });
}

async function processEvent(event: Event, customerId: string, productId: string) {
    /**Process a single agent event*/

    // Make AI calls as needed
    if (event.type === "email_received") {
        const response = await openaiClient.chat.completions.create({
            model: "gpt-4",
            messages: [{ role: "user", content: event.content }]
        });
        await handleEmailResponse(response);

        // Send signal
        await sendSignal("email_processed", customerId, productId);
    }
    else if (event.type === "call_completed") {
        // Different AI calls for call processing
        const summary = await openaiClient.chat.completions.create({
            model: "gpt-4",
            messages: [{ role: "user", content: `Summarize: ${event.transcript}` }]
        });

        await sendSignal("call_summarized", customerId, productId);
    }
}

// Event loop
while (true) {
    const event = await getNextEvent();  // Your event queue
    if (event) {
        await processEvent(event, "customer_123", "agent_123");
    }
}
```

## Best Practices

### 1. One Signal Per Business Outcome

Each business outcome should have one signal:

#### Python

```python
from paid import Paid

paid_client = Paid(token="YOUR_PAID_API_KEY")

def send_signal(event_name, customer_id, product_id, data=None):
    signal = {
        "event_name": event_name,
        "customer": {"external_customer_id": customer_id},
        "attribution": {"external_product_id": product_id},
        "data": data or {}
    }
    paid_client.signals.create_signals(signals=[signal])

# ✅ Good: One signal per outcome
def book_meeting(prospect, customer_id):
    # Multiple AI calls OK
    analyze_calendar(prospect)
    generate_invite(prospect)
    send_confirmation(prospect)

    # Single signal for the outcome
    send_signal("meeting_booked", customer_id, "agent_123")

# ❌ Bad: Multiple signals for same workflow
def process_workflow(prospect, customer_id):
    analyze_calendar(prospect)
    send_signal("calendar_analyzed", customer_id, "agent_123")  # Too granular

    generate_invite(prospect)
    send_signal("invite_generated", customer_id, "agent_123")   # Too granular
```

#### Node.js

```typescript
import { PaidClient } from "@paid-ai/paid-node";

const paidClient = new PaidClient({ token: "YOUR_PAID_API_KEY" });

async function sendSignal(eventName: string, customerId: string, productId: string, data?: any) {
    await paidClient.signals.createSignals({
        signals: [{
            eventName,
            customer: { externalCustomerId: customerId },
            attribution: { externalProductId: productId },
            data: data || {}
        }]
    });
}

// ✅ Good: One signal per outcome
async function bookMeeting(prospect: Prospect, customerId: string) {
    // Multiple AI calls OK
    await analyzeCalendar(prospect);
    await generateInvite(prospect);
    await sendConfirmation(prospect);

    // Single signal for the outcome
    await sendSignal("meeting_booked", customerId, "agent_123");
}

// ❌ Bad: Multiple signals for same workflow
async function processWorkflow(prospect: Prospect, customerId: string) {
    await analyzeCalendar(prospect);
    await sendSignal("calendar_analyzed", customerId, "agent_123");  // Too granular

    await generateInvite(prospect);
    await sendSignal("invite_generated", customerId, "agent_123");   // Too granular
}
```

### 2. Use Idempotency Keys

Always include idempotency keys to prevent duplicate signals:

#### Python

```python
from paid import Paid

paid_client = Paid(token="YOUR_PAID_API_KEY")

signal = {
    "event_name": "email_sent",
    "customer": {"external_customer_id": "customer_123"},
    "attribution": {"external_product_id": "agent_123"},
    "idempotency_key": f"email_{email_id}_{timestamp}"  # Unique per event
}

paid_client.signals.create_signals(signals=[signal])
```

#### Node.js

```typescript
import { PaidClient } from "@paid-ai/paid-node";

const paidClient = new PaidClient({ token: "YOUR_PAID_API_KEY" });

await paidClient.signals.createSignals({
    signals: [{
        eventName: "email_sent",
        customer: { externalCustomerId: "customer_123" },
        attribution: { externalProductId: "agent_123" },
        idempotencyKey: `email_${emailId}_${timestamp}`  // Unique per event
    }]
});
```

## Troubleshooting

### Signals Not Appearing in Dashboard

If signals aren't showing up:

1. **Check API key**: Verify your API key is valid and has the correct permissions
2. **Check customer exists**: The customer ID must exist in your Paid organization
3. **Check product exists**: If using attribution, the product must exist
4. **Check response**: Verify the API response shows `ingested: 1`

### Duplicate Signals

If you're seeing duplicate signals:

* Add `idempotencyKey` to your signals
* Use a unique identifier for each business event (e.g., `{event_type}_{entity_id}_{timestamp}`)

## Next Steps

* [View your signals in the dashboard](https://app.paid.ai/signals)
* [View your costs in the dashboard](https://app.paid.ai/vendors)

## Need Help?

* **Email**: [support@paid.ai](mailto:support@paid.ai)
* **Community**: Join `#paid-developers` on Slack

## Additional Resources

* [Python SDK Documentation](https://github.com/paid-ai/paid-python)
* [Node.js SDK Documentation](https://github.com/paid-ai/paid-node)