---
id: "api-reference/track-lead"
type: "doc"
url: "https://affitor.com/docs/api-reference/track-lead"
---
# Track Lead

> Record a signup or lead event that links a customer to an affiliate click.

`POST /api/v1/track/lead`

Records a lead event for a customer who signed up through an affiliate referral. Call this after a successful registration so Affitor can advance the attribution chain from click → lead.

---

## Authentication

| Mode | When to use | How |
|------|-------------|-----|
| **Server mode** (recommended) | Your backend calls the API after a signup | `Authorization: Bearer <program_api_token>` |
| **Browser mode** | The tracker's `signup()` call from the page | No `Authorization` header — the call carries only the `click_id` |

The mode is decided by the header: a request with a valid Bearer key is server mode, and a request without one is browser mode. In server mode, Affitor validates the key and checks that the customer belongs to that program.

### Lead auth policy

Each program has a lead auth policy. **New programs default to server-verified leads** (`server_required`). Under that policy, a browser-mode lead is recorded as **provisional**:

- the API still returns `200`, with `provisional: true` in the body
- the lead does not settle attribution, does not save your `customer_key` on the customer, and does not count in partner metrics or pay a per-lead commission
- it settles only when your backend sends the same lead with a Bearer key, or when a Stripe Checkout payment for that customer arrives through your connected Stripe account. A sale sent to `/api/v1/track/sale` does not settle it, and a Bearer lead sent after that sale returns `400`

Programs created before this policy existed keep the older behavior, where a browser lead settles directly. Send leads from your server with a Bearer key so the lead settles under either policy.

---

## Request

### Headers

<ParamList>
  <ParamField name="Authorization" type="header" requirement="conditional">
    `Bearer <program_api_token>` — send it for server mode. Without it, the call is treated as browser mode.
  </ParamField>
  <ParamField name="Content-Type" type="header" required>
    Must be `application/json`.
  </ParamField>
</ParamList>

### Body fields

<ParamList>
  <ParamField name="click_id" type="string" requirement="conditional">
    The `affitor_click_id` value from the affiliate click cookie. Required outside test mode unless `customer_key` is provided.
  </ParamField>
  <ParamField name="customer_key" type="string" requirement="conditional">
    Your internal customer or user ID. Required outside test mode unless `click_id` is provided.
  </ParamField>
  <ParamField name="email" type="string">
    Customer email address. Used for hashed/masked attribution support.
  </ParamField>
  <ParamField name="additional_data" type="object">
    Optional metadata. Supports `test_mode` (boolean), `program_id` (number), and `event_name` (string).
  </ParamField>
</ParamList>

Outside test mode, **at least one of** `click_id` or `customer_key` must be provided. Providing both is recommended for reliable downstream payment attribution.

### Request body example

<CodeGroup items={[
  {
    label: "cURL",
    lang: "bash",
    code: `curl -X POST https://api.affitor.com/api/v1/track/lead \\
  -H "Authorization: Bearer YOUR_PROGRAM_API_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{
    "click_id": "cust_42_1234567890",
    "customer_key": "usr_abc123",
    "email": "user@example.com"
  }'`
  },
  {
    label: "JSON body",
    lang: "json",
    code: `{
  "click_id": "cust_42_1234567890",
  "customer_key": "usr_abc123",
  "email": "user@example.com"
}`
  },
]} />

---

## Response

### Success — 200

```json
{
  "success": true,
  "message": "Lead tracked successfully"
}
```

### Success — provisional browser lead (200)

Returned for a browser-mode lead (no Bearer key) when the program requires server-verified leads. The lead is stored but not settled; see [Lead auth policy](#lead-auth-policy).

```json
{
  "success": true,
  "provisional": true,
  "message": "Lead recorded as provisional. This program requires server-side (Bearer) lead tracking to settle attribution."
}
```

### Success — test mode (200)

When `additional_data.test_mode` is `true`, the response includes the created event details:

```json
{
  "success": true,
  "message": "Test lead event tracked successfully",
  "data": {
    "eventId": 456,
    "programId": 1,
    "test_mode": true
  }
}
```

---

## Errors

| Status | Condition |
|--------|-----------|
| `400 Bad Request` | `click_id` and `customer_key` are both missing (outside test mode) |
| `400 Bad Request` | No customer record found for the provided `click_id` or `customer_key` |
| `400 Bad Request` | Customer does not belong to the authenticated program (server mode cross-program mismatch) |
| `400 Bad Request` | Customer status cannot transition to lead (e.g. already converted) |
| `401 Unauthorized` | `Authorization` header is present but the token is invalid or not found |
| `500 Internal Server Error` | Unexpected error during lead event creation |

---

## Test Mode

Send `additional_data.test_mode: true` to create a test lead event without affecting production attribution. In test mode:

- a Bearer key is not required to store the test event, but only a test event sent with your program API key counts toward integration verification
- pass `additional_data.program_id` to associate the test event with a specific program
- the event is created with `is_test: true`

```json
{
  "click_id": "test_lead_001",
  "customer_key": "test_customer",
  "additional_data": {
    "test_mode": true,
    "program_id": 1
  }
}
```

---

## Related

- [Click Tracking](/docs/brand/tracking/click-tracking/) — must run before lead tracking
- [Lead Tracking Guide](/docs/brand/tracking/lead-tracking-signup/) — implementation examples (Node.js, Python, browser)
- [Track Sale](/docs/api-reference/track-sale/) — next step after a successful lead
