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

# Objectives & conversions

> Tell Notifizz which event counts as a conversion, see who actually converted.

## TL;DR

An **objective** is the event Notifizz watches to decide a campaign "worked". You set it on the campaign (a click, a read, a custom event like `purchase.completed`). Notifizz then credits one conversion per unique recipient who received the campaign and triggered the objective afterwards.

* Pick from **`Click`**, **`Read`** or any of your custom events.
* Conversions are **deduped per recipient** — the same user doesn't count twice.
* The dashboard shows **who** converted (last 50) and **every objective evaluation** in the Outbox tab for debugging.

## Setting an objective

In the campaign editor, the recipients box has an objective row at the bottom. Click it to choose:

* **`Click`** — fires when a recipient clicks any button you placed in the notification. No identity work needed: Notifizz wires the click through a redirect URL it generates per message.
* **`Read`** — fires when the notification is marked read in the inbox widget.
* **A custom event** — any event in your catalog. Notifizz credits the conversion when the event payload carries a canonical identity (`id`, `email`, `pushId`, or `phone`) that matches a recipient who got this campaign.

The objective is **per campaign**: two campaigns targeting the same event each get their own credit count.

## How conversions are credited

Two paths exist, both honour the same dedup rule (one credit per recipient per campaign).

### Fast path — receipt-based

The default for `Click`, `Read`, and custom events whose payload already carries identity. Notifizz looks up "did this recipient receive a message from this campaign?" — if yes, credits a conversion. One Firestore lookup, no custom code.

```ts theme={null}
// Your SDK call — identity flows through `properties`
client.track('purchase.completed', {
    properties: {
        email: 'alice@example.com',  // canonical identity
        amount: 4990,
    },
});
```

### Custom resolution — `onObjective`

When the event doesn't carry canonical identity directly (only an internal ref, a Stripe customer id, etc.), your orchestrator can export a second hook that resolves it server-side:

```ts theme={null}
export const onObjective = async (event: Event, sdk: Sdk): Promise<Identity | null> => {
    const { user } = await sdk.enrichWith('UserByStripeCustomer', {
        stripeCustomerId: event.properties.stripeCustomerId,
    });
    if (!user) return null;
    return { id: user.id, email: user.email };
};
```

The hook returns `{ id?, email?, pushId?, phone? } | null`. `null` means "this event does not qualify" — fine, the conversion is simply not credited.

Notifizz routes the event through a cloud function only when this hook is exported. Without it, the fast path runs.

## Who converted

The campaign performance panel surfaces the latest 50 converted recipients — first identity, relative time, source event. Click the row to drill into the message that ultimately produced the conversion.

## Debugging in the Outbox

The Outbox has two tabs:

* **Notifications** — every workflow run (delivered, no-recipient, failed, in-progress).
* **Objectives** — every objective evaluation, regardless of outcome.

Objective outcomes you'll see:

| Outcome           | What it means                                                                  |
| ----------------- | ------------------------------------------------------------------------------ |
| `Reached`         | First time this recipient triggered the objective on this campaign — counted.  |
| `Already counted` | Same recipient triggered it again — deduped, no double credit.                 |
| `No message`      | The event matched the objective but the recipient never received the campaign. |
| `No identity`     | No canonical field on the event (or `onObjective` returned `null`).            |
| `Failed`          | `onObjective` threw or timed out — fix the code, the next event runs fresh.    |

Click any row to see the inflated event payload, the resolved identity, condition traces, and any error.

## Privacy

Notifizz holds no customer data — only the canonical identity fields needed to dedup conversions (`id`, `email`, `pushId`, `phone`), stored on the dedup index. Conversion rows for the "Who converted" widget are pruned on the same retention schedule as workflow runs.
