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

# Server-Side Conversions

> Send conversion events from your backend via the REST API

Server-side conversion tracking lets you record conversions that happen on your backend — payment confirmations, form submissions, CRM updates, or webhook-triggered events. These events are automatically attributed to the visitor's browser session and any active experiments, with no extra analytics configuration needed.

## How It Works

1. **Create an API key** in your pagent dashboard under Settings > API Keys
2. **Capture session variables** on the frontend using `window._pgnt.variables("goal_label")`
3. **Pass the variables** to your backend (e.g., in a form submission or AJAX request)
4. **Send a conversion event** from your backend via the REST API — use `POST /v1/conversions` or `POST /v2/conversions`

Both v1 and v2 are fully supported. v2 accepts the output of `_pgnt.variables()` directly as a `session_data` object, which is useful when your integration cannot destructure the variables before sending.

> **Important**: The `session_id` links each server-side event to the visitor's browser session and active experiments. Without a valid `session_id`, the conversion cannot be attributed to an experiment.

***

## Prerequisites

* The [pagent SDK](/guides/integration/sdk-integration) installed on your website
* At least one goal configured in the **Tracking** section of your dashboard (see [Tracking Revenue](/guides/integration/tracking-revenue) for setup instructions)

***

## 1. Create an API Key

1. Navigate to **Settings > API Keys** in your pagent dashboard
2. Click **"Create API Key"**
3. Give it a descriptive name (e.g., "Production Backend")
4. Copy the API key immediately

> **Important**: The API key is displayed only once at creation. Store it securely as an environment variable. If you lose the key, delete it and create a new one.

***

## 2. Capture Variables from the SDK

Use `window._pgnt.variables()` to get the session identifiers and goal UUID you need for the API call. Pass the goal label as a string argument:

```javascript theme={"dark"}
const vars = window._pgnt.variables("purchase");
// {
//   session_id: "abc123...",
//   user_id: "xyz789...",
//   visit_id: "v_456...",
//   conversion_id: "goal-uuid-here"
// }
```

| Property        | Description                                                |
| --------------- | ---------------------------------------------------------- |
| `session_id`    | The current browser session identifier                     |
| `user_id`       | The visitor's persistent user identifier                   |
| `visit_id`      | The current page view identifier                           |
| `conversion_id` | UUID of the goal matching the label, or `null` if no match |

> **Note**: If `conversion_id` is `null`, the label does not match any configured goal. Double-check the goal label in your Tracking settings.

See [Variables](/guides/integration/variables) for the complete list of fields the method returns, including experiment identifiers.

### Passing Variables to Your Backend

Send the captured variables alongside your existing request data:

```javascript theme={"dark"}
const vars = window._pgnt.variables("purchase");

fetch("/api/checkout", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
        cart_id: "cart_abc123",
        pagent: vars  // pass the whole object — v2 accepts it directly
    })
});
```

***

## 3. v1 API

### Endpoint

|                   |                                           |
| ----------------- | ----------------------------------------- |
| **URL**           | `https://ingest.pagent.ai/v1/conversions` |
| **Method**        | `POST`                                    |
| **Authorization** | `Bearer <API_KEY>`                        |
| **Content-Type**  | `application/json`                        |

### Request Body

The request body contains an `events` array with 1 to 100 conversion events:

```json theme={"dark"}
{
    "events": [
        {
            "session_id": "abc123",
            "user_id": "xyz789",
            "conversion_id": "goal-uuid-here",
            "label": "purchase",
            "revenue": 2499
        }
    ]
}
```

| Field           | Type            | Required | Description                                               |
| --------------- | --------------- | -------- | --------------------------------------------------------- |
| `session_id`    | string          | Yes      | From `_pgnt.variables()`                                  |
| `user_id`       | string          | Yes      | From `_pgnt.variables()`                                  |
| `conversion_id` | string          | Yes      | Goal UUID from `_pgnt.variables()`                        |
| `label`         | string          | No       | Human-readable goal tag (useful for debugging)            |
| `revenue`       | integer         | No       | Value in cents (e.g., `999` = \$9.99)                     |
| `properties`    | object or array | No       | Custom metadata about the conversion                      |
| `time`          | number          | No       | Unix timestamp in milliseconds (defaults to current time) |

### Response

| Status | Body                 | Description                          |
| ------ | -------------------- | ------------------------------------ |
| `200`  | `{ "accepted": 1 }`  | Events accepted successfully         |
| `400`  | `{ "error": "..." }` | Invalid request body                 |
| `401`  | `{ "error": "..." }` | Missing, invalid, or expired API key |
| `429`  |                      | Rate limit exceeded                  |

### Code Examples

#### cURL

```bash theme={"dark"}
curl -X POST https://ingest.pagent.ai/v1/conversions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "session_id": "abc123",
      "user_id": "xyz789",
      "conversion_id": "goal-uuid-here",
      "label": "purchase",
      "revenue": 2499
    }]
  }'
```

#### Node.js

```javascript theme={"dark"}
const PAGENT_API_KEY = process.env.PAGENT_API_KEY;

async function trackConversion({ session_id, user_id, conversion_id, label, revenue }) {
    const response = await fetch("https://ingest.pagent.ai/v1/conversions", {
        method: "POST",
        headers: {
            "Authorization": `Bearer ${PAGENT_API_KEY}`,
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            events: [{ session_id, user_id, conversion_id, label, revenue }]
        })
    });

    if (!response.ok) {
        const body = await response.json();
        throw new Error(`Pagent API error (${response.status}): ${body.error}`);
    }

    return response.json(); // { accepted: 1 }
}

// Usage in an Express route handler
app.post("/api/checkout", async (req, res) => {
    const { session_id, user_id, conversion_id, cart_total } = req.body;

    // Process the checkout...
    const order = await processCheckout(req.body);

    // Track the conversion
    await trackConversion({
        session_id,
        user_id,
        conversion_id,
        label: "purchase",
        revenue: Math.round(cart_total * 100) // Convert dollars to cents
    });

    res.json({ order_id: order.id });
});
```

#### Python

```python theme={"dark"}
import os
import requests

PAGENT_API_KEY = os.environ["PAGENT_API_KEY"]

def track_conversion(session_id, user_id, conversion_id, label=None, revenue=None):
    response = requests.post(
        "https://ingest.pagent.ai/v1/conversions",
        headers={
            "Authorization": f"Bearer {PAGENT_API_KEY}",
            "Content-Type": "application/json",
        },
        json={
            "events": [{
                "session_id": session_id,
                "user_id": user_id,
                "conversion_id": conversion_id,
                "label": label,
                "revenue": revenue,
            }]
        },
    )
    response.raise_for_status()
    return response.json()  # {"accepted": 1}
```

### Batch Events

You can send up to 100 events in a single request. Events can belong to different sessions and users, making this ideal for processing webhooks or background jobs:

```json theme={"dark"}
{
    "events": [
        {
            "session_id": "sess_001",
            "user_id": "user_a",
            "conversion_id": "goal-uuid",
            "label": "purchase",
            "revenue": 2499
        },
        {
            "session_id": "sess_002",
            "user_id": "user_b",
            "conversion_id": "goal-uuid",
            "label": "purchase",
            "revenue": 4999
        }
    ]
}
```

### Custom Properties

Attach custom metadata to your conversion events for additional context. The `properties` field accepts an object or an array of objects.

```json theme={"dark"}
{
    "events": [{
        "session_id": "abc123",
        "user_id": "xyz789",
        "conversion_id": "goal-uuid",
        "label": "purchase",
        "revenue": 2499,
        "properties": {
            "product_id": "prod_123",
            "category": "electronics",
            "payment_method": "credit_card"
        }
    }]
}
```

***

## 4. v2 API

v2 is useful when your integration cannot destructure `_pgnt.variables()` before sending — the session identifiers are grouped in a `session_data` object that you can pass through directly.

### Single Event Endpoint

|                   |                                           |
| ----------------- | ----------------------------------------- |
| **URL**           | `https://ingest.pagent.ai/v2/conversions` |
| **Method**        | `POST`                                    |
| **Authorization** | `Bearer <API_KEY>`                        |
| **Content-Type**  | `application/json`                        |

#### Request Body

```json theme={"dark"}
{
    "session_data": {
        "session_id": "abc123",
        "user_id": "xyz789",
        "visit_id": "v_456",
        "conversion_id": "goal-uuid-here"
    },
    "label": "purchase",
    "revenue": 2499
}
```

| Field                        | Type            | Required | Description                                               |
| ---------------------------- | --------------- | -------- | --------------------------------------------------------- |
| `session_data`               | object          | Yes      | Session identifiers from `_pgnt.variables()`              |
| `session_data.session_id`    | string          | Yes      | The current browser session identifier                    |
| `session_data.user_id`       | string          | Yes      | The visitor's persistent user identifier                  |
| `session_data.visit_id`      | string          | No       | The current page view identifier                          |
| `session_data.conversion_id` | string          | Yes      | Goal UUID from `_pgnt.variables()`                        |
| `label`                      | string          | No       | Human-readable goal tag (useful for debugging)            |
| `revenue`                    | integer         | No       | Value in cents (e.g., `999` = \$9.99)                     |
| `properties`                 | object or array | No       | Custom metadata about the conversion                      |
| `time`                       | number          | No       | Unix timestamp in milliseconds (defaults to current time) |

#### Response

| Status | Body                 | Description                          |
| ------ | -------------------- | ------------------------------------ |
| `200`  | `{ "accepted": 1 }`  | Event accepted successfully          |
| `400`  | `{ "error": "..." }` | Invalid request body                 |
| `401`  | `{ "error": "..." }` | Missing, invalid, or expired API key |
| `429`  |                      | Rate limit exceeded                  |

### Bulk Endpoint

|                   |                                                |
| ----------------- | ---------------------------------------------- |
| **URL**           | `https://ingest.pagent.ai/v2/conversions/bulk` |
| **Method**        | `POST`                                         |
| **Authorization** | `Bearer <API_KEY>`                             |
| **Content-Type**  | `application/json`                             |

Send up to 100 events in a single request. Each event uses the same `session_data` structure as the single-event endpoint:

```json theme={"dark"}
{
    "events": [
        {
            "session_data": {
                "session_id": "sess_001",
                "user_id": "user_a",
                "conversion_id": "goal-uuid"
            },
            "label": "purchase",
            "revenue": 2499
        },
        {
            "session_data": {
                "session_id": "sess_002",
                "user_id": "user_b",
                "conversion_id": "goal-uuid"
            },
            "revenue": 4999
        }
    ]
}
```

### Code Examples

#### cURL

```bash theme={"dark"}
curl -X POST https://ingest.pagent.ai/v2/conversions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "session_data": {
      "session_id": "abc123",
      "user_id": "xyz789",
      "visit_id": "v_456",
      "conversion_id": "goal-uuid-here"
    },
    "label": "purchase",
    "revenue": 2499
  }'
```

#### Node.js

```javascript theme={"dark"}
const PAGENT_API_KEY = process.env.PAGENT_API_KEY;

async function trackConversion(sessionData, { label, revenue } = {}) {
    const response = await fetch("https://ingest.pagent.ai/v2/conversions", {
        method: "POST",
        headers: {
            "Authorization": `Bearer ${PAGENT_API_KEY}`,
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            session_data: sessionData,
            label,
            revenue
        })
    });

    if (!response.ok) {
        const body = await response.json();
        throw new Error(`Pagent API error (${response.status}): ${body.error}`);
    }

    return response.json(); // { accepted: 1 }
}

// Usage in an Express route handler
app.post("/api/checkout", async (req, res) => {
    const { pagent, cart_total } = req.body;
    // pagent is the raw _pgnt.variables() output passed from the frontend

    // Process the checkout...
    const order = await processCheckout(req.body);

    // Track the conversion — pass session_data directly, no destructuring needed
    await trackConversion(pagent, {
        label: "purchase",
        revenue: Math.round(cart_total * 100) // Convert dollars to cents
    });

    res.json({ order_id: order.id });
});
```

#### Python

```python theme={"dark"}
import os
import requests

PAGENT_API_KEY = os.environ["PAGENT_API_KEY"]

def track_conversion(session_data, label=None, revenue=None):
    response = requests.post(
        "https://ingest.pagent.ai/v2/conversions",
        headers={
            "Authorization": f"Bearer {PAGENT_API_KEY}",
            "Content-Type": "application/json",
        },
        json={
            "session_data": session_data,
            "label": label,
            "revenue": revenue,
        },
    )
    response.raise_for_status()
    return response.json()  # {"accepted": 1}
```

### Custom Properties

Attach custom metadata using the `properties` field (accepts an object or an array of objects):

```json theme={"dark"}
{
    "session_data": {
        "session_id": "abc123",
        "user_id": "xyz789",
        "conversion_id": "goal-uuid"
    },
    "label": "purchase",
    "revenue": 2499,
    "properties": {
        "product_id": "prod_123",
        "category": "electronics",
        "payment_method": "credit_card"
    }
}
```

***

## Tracking Revenue

Revenue values follow the same format as [client-side tracking](/guides/integration/tracking-revenue): integers representing **cents**, not dollars.

* Correct: `"revenue": 999` (represents \$9.99)
* Incorrect: `"revenue": 9.99` (will be treated as 9 cents)

| Dollar Amount | Revenue Value |
| ------------- | ------------- |
| \$1.00        | `100`         |
| \$9.99        | `999`         |
| \$25.50       | `2550`        |
| \$100.00      | `10000`       |

***

## Best Practices

1. **Store API keys securely** — use environment variables, never commit keys to source control
2. **Send events promptly** — if there is a delay between the conversion and the API call, use the `time` field to record the actual conversion time
3. **Validate before sending** — check that `conversion_id` is not `null` before passing variables to your backend
4. **Batch when possible** — group events into a single request to reduce HTTP overhead
5. **Handle errors gracefully** — retry on `500` responses, do not retry on `400` or `401`
6. **Revenue in cents** — always convert dollar amounts to integer cents before sending

***

## Troubleshooting

### Conversions Not Appearing

* Verify `session_id` is from an active session captured by the SDK
* Check that the goal exists in your Tracking settings and `conversion_id` is correct
* Confirm the API key has not expired (keys expire after 1 year by default)

### 401 Unauthorized

* Confirm the `Authorization: Bearer <key>` header is present and correctly formatted
* The API key may be expired or deleted — create a new one in Settings > API Keys
* Make sure there are no extra spaces or newlines in the key value

### 400 Bad Request

* **v2**: `session_data` must be an object containing `session_id`, `user_id`, and `conversion_id`
* **v2 bulk / v1**: The `events` array must contain 1 to 100 items
* **v1**: `session_id`, `user_id`, and `conversion_id` are all required as top-level fields on each event
* `revenue` must be an integer, not a decimal (e.g., `999` not `9.99`)

### Need Help?

Reach out at [support@pagent.ai](mailto:support@pagent.ai).
