Skip to main content
For Encore to measure whether its offers drive your subscriptions, each subscription event must join back to the offer exposure Encore recorded for the same person. The whole integration reduces to one rule: one stable customer id everywhere. Concretely, that is two steps:
  1. Identify with your stable customer id before presenting offers. Encore.identify(customerId) keys every exposure and claim to that id.
  2. Forward each subscription event server-side to Encore’s webhook with user.app_account_id set to that same id, byte-identical, plus the matching event verb.
Encore joins your events to offer exposures by app_account_id. That is the whole job: no id plumbing through your checkout, no other client work.

Using RevenueCat?

If RevenueCat manages your subscriptions, you are most likely done already. Encore natively ingests RevenueCat webhooks, so you never build the server-side forwarding in step 2 yourself. The whole recipe is two alignment moves:
  1. Identify with your RevenueCat app user id. Call Encore.identify() with the same id you pass to RevenueCat as app_user_id (equivalently, use one stable customer id for both). This is step 1 below with one extra constraint: the two ids must match.
  2. Point RevenueCat’s webhook forwarding at Encore. Add the per-app receiver URL from your Encore dashboard’s webhook settings as a RevenueCat webhook target. See RevenueCat’s webhook guide for how to add a webhook in their dashboard. Many publishers already have this configured; if so, just confirm the URL.
That is the entire integration: no payload construction, no signing, no event mapping. Encore links RevenueCat’s subscription events to your offer exposures server-side using the shared id. If this is you, you are done; the manual steps below are for everyone else.

Step 1: identify before you show offers

Call identify() with your own stable customer id, before any offer is presented:
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() returns. Without identify(), the SDK falls back to an auto-generated anonymous id, and your server has no way to reproduce that value later, so your subscription events can never link back. On web this is critical: identify as soon as you know who the user is, and always before 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. This guide only composes it:
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);
});

Using Another Billing Provider?

If your subscriptions run through Stripe, Paddle, or your own custom billing rather than RevenueCat, the two manual steps above are your path. Identify with your stable customer id (step 1), then forward each subscription event server-side to Encore’s webhook with that same id (step 2). That is the canonical path for any biller Encore does not ingest natively; there is no other client work.

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.
  • 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.