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

# Configure Analytics

> Forward your subscription events to Encore with one stable customer id so it can measure offer impact.

## Overview

Encore measures offer impact by joining your subscription events back to the offer exposures it recorded for the same person. Until those events reach Encore, the Analytics section of your dashboard shows no data.

The whole integration reduces to one rule: **one stable customer id everywhere**.

<Note>
  **Using a third-party subscription manager?** If RevenueCat manages your subscriptions, it can carry these events for you: see [RevenueCat](/publishers/web/guides/managers/revenuecat).
</Note>

***

## Step 1: Identify before you show offers

Call [`identify()`](/publishers/web/sdk-reference/identify) with your own stable customer id, before any offer is presented:

```javascript theme={null}
import Encore from '@encorekit/web-sdk';

Encore.identify('cus_8f4kq2'); // your stable customer id, e.g. your database user id
```

The id you pass becomes the **app account id** that keys every exposure and claim Encore records; it is the value [`getAppAccountId()`](/publishers/web/sdk-reference/webhook-ingestion#getappaccountid) returns. Without `identify()`, the SDK falls back to an auto-generated anonymous id that your server cannot reproduce later, so those exposures can never link back. Identify as soon as you know who the user is, and always before [`show()`](/publishers/web/sdk-reference/show).

***

## Step 2: Forward subscription events with the same id

Whenever a subscription event happens in your billing system (subscribe, renew, cancel, and so on), your server POSTs it to `https://api.encorekit.com/encore/webhooks/encore` with:

* `user.app_account_id`: the **same customer id** from step 1, byte-identical
* `subscription.original_transaction_id`: your biller's subscription id
* `event_type`: the matching Encore verb, for example `did_subscribe` or `did_renew`

The full contract (HMAC auth headers, the `buildEncoreHeaders` signing helper, the payload schema, the verb list, and responses) lives in the [Webhook Ingestion reference](/publishers/web/sdk-reference/webhook-ingestion). This step only composes it:

```javascript theme={null}
import { buildEncoreHeaders } from './encore-signing'; // the reference's signing helper

const ENCORE_URL = 'https://api.encorekit.com/encore/webhooks/encore';

async function forwardToEncore({ verb, eventId, occurredAt, subscriptionId, customerId }) {
  const rawBody = JSON.stringify({
    event_id: eventId,                    // unique per event; Encore dedups on it
    event_type: verb,                     // e.g. 'did_subscribe'; full list in the reference
    occurred_at: occurredAt,              // RFC3339 UTC with a trailing Z
    user: { app_account_id: customerId }, // the SAME id you passed to identify()
    subscription: { original_transaction_id: subscriptionId, store: 'stripe' },
  });

  const headers = buildEncoreHeaders(rawBody, {
    publishableKey: 'pk_live_your_publishable_key',
    secretKey: process.env.ENCORE_SECRET_KEY,
  });

  await fetch(ENCORE_URL, { method: 'POST', headers, body: rawBody }); // send the same bytes
}

// In your billing webhook handler (Stripe shown; any biller works the same):
app.post('/webhooks/billing', async (req, res) => {
  const event = req.body;

  if (event.type === 'customer.subscription.created') {
    // Resolve YOUR customer id from your own records (the subscription-to-customer
    // mapping in your database), never from anything the browser sent.
    const customerId = await lookupCustomerId(event.data.object.customer);

    await forwardToEncore({
      verb: 'did_subscribe',
      eventId: event.id,
      occurredAt: new Date(event.created * 1000).toISOString(),
      subscriptionId: event.data.object.id,
      customerId,
    });
  }

  res.sendStatus(200);
});
```

***

## Gotchas

* **Byte-identical id.** `user.app_account_id` must equal the `identify()` id exactly; any prefixing, casing, or trimming difference breaks the join silently.
* **Identify before show, not after.** Exposures recorded before `identify()` are keyed to the anonymous id and are not retro-linked; a late identify leaves them unattributed.
* **Resolve the id server-side.** Read the customer id from your own auth session or subscription records, never from the client request body: a spoofed id attributes someone else's subscription.
* **Every event carries the id.** `user.app_account_id` is required on every event, not just the first; an event without it is acknowledged but dropped. See [how the join works](/publishers/web/sdk-reference/webhook-ingestion#how-the-join-works).
* **Use the canonical verbs.** An unrecognized `event_type` is accepted and stored but cannot be projected into measurement; map your biller's events to the [verb list](/publishers/web/sdk-reference/webhook-ingestion#field-reference).

***

## Next Steps

You've completed the Encore web integration. Here's what to explore next:

* [Encore Dashboard](https://dashboard.encorekit.com) - Monitor analytics and manage your app
* [Webhook Ingestion reference](/publishers/web/sdk-reference/webhook-ingestion) - The full contract this step forwards to
* [Reduce Churn](/publishers/use-cases/reduce-churn) - The use case this integration serves, and the moments to target
