Skip to main content
POST /webhooks/encore is the server-to-server endpoint for forwarding your subscription lifecycle events into Encore. Encore joins each event back to the web exposure it recorded for the same user, which is what powers NCL (incrementality) measurement. This page is the contract: endpoint, authentication, signing, payload schema, responses, and idempotency. For the task-level walkthrough, see Configure Analytics.
This is the inbound direction: your server → Encore. For the outbound direction (Encore calling your server when a user completes an offer), see Offer Completed Webhook. Different headers, a different signing string, and an unrelated purpose; don’t mix the two.

When to use this

Use this if you bill your own subscriptions (for example, Stripe) and want those events in Encore for incrementality measurement. Forward each subscription event from your server as it happens. If your subscriptions run through a source that already notifies us (RevenueCat, Superwall, or App Store / Play server notifications), point those at their dedicated receivers instead; do not also send them here.

Endpoint

One request carries exactly one canonical subscription event. Send events as they happen in your billing system.

Authentication

Requests use the platform’s standard server-to-server HMAC. Send three headers:

Signing a request

The signed string is not the body alone; it is the composite:
  • <unix_timestamp> is the exact value you send in X-Timestamp.
  • The method is the literal POST.
  • The path is the literal /webhooks/encore. Note this is the path the API serves internally, without the /encore prefix that appears in the public URL. Sign /webhooks/encore, even though you POST to …/encore/webhooks/encore.
  • <raw_body> is the exact bytes of the JSON body you transmit. Serialize once, then sign and send the identical bytes; do not re-serialize after signing (property reordering or whitespace changes will break the signature).
This buildEncoreHeaders helper is the one signing implementation referenced everywhere in these docs:

Payload

The request body is JSON.

Structure

Field Reference

Example payload

The canonical example; both required identifiers (user.app_account_id and subscription.original_transaction_id) are present, as they must be on every event:
A later event (for example did_renew) has the same shape and still carries user.app_account_id; only the lifecycle-specific fields differ.

How the join works

Encore links subscription.original_transaction_id (the chain anchor) to user.app_account_id (the persistent person id) on every event, and that link is what resolves a subscription back to the web exposure Encore recorded for the same person. Because the link is (re)written on every event, user.app_account_id must be present on every event, not just the first. An event missing it fails validation and is dropped, so that subscription cannot be attributed. Persist the app account id when you first see a subscription and attach it to every event you forward. Configure Analytics shows this end to end.

Responses

object
The event was authenticated, validated, and forwarded for processing.
object
The request authenticated, but an internal error past auth occurred (for example a malformed JSON body or a payload that fails validation, such as a missing user.app_account_id). Encore returns 200 here deliberately, to prevent retry storms.Important: a 200 is not proof of durable storage. The "processing deferred" message specifically means the event was acknowledged but may have been dropped. Treat only the "Received <event_id>" message as a successful ingest; if you see "processing deferred", inspect your payload (most often a validation failure).
object
Authentication failed: missing HMAC headers, a signature mismatch, an unknown key, or an X-Timestamp outside the ±300 second window. Auth failures fail loud (unlike post-auth errors). Note the shape: error responses use Encore’s platform error body, a top-level error message with no success field. Only 200 responses carry the success envelope.

Idempotency

event_id is the dedup key end to end. Re-POSTing an event with the same event_id collapses to a single stored event, so retries are safe.

getAppAccountId()

Returns the persistent Encore app account id for the current user: the value to carry through your billing provider and send as user.app_account_id.

Signature

Return Value

Type: string | null
  • Your own user id if identify() was called.
  • An auto-generated anonymous id otherwise.
  • null if the SDK has not initialized yet.
Call Encore.identify(yourStableUserId) so the app account id is your own stable user id rather than an anonymous, per-device id. The same user then resolves to the same app_account_id everywhere, making the join robust across devices and sessions.
getAppAccountId() is the canonical accessor for this use case. getCurrentUserId() returns the same underlying id.

Next steps

  • Configure Analytics: the quickstart step that uses this contract.
  • identify(): set a stable user id so the app account id is consistent across devices.