Skip to main content
Skip to navigation

Transactions feed

Columns, sample queries, and handling guidance for the Transactions Scheduled Data Export feed

Ask RicoChatGPTClaude

This page lists the columns of the Transactions feed, explains how to handle updated transactions, and gives sample queries. For setup, delivery frequency, and file formats, see Scheduled Data Exports.

The Transactions feed exports per-transaction Customer and revenue data. RevenueCat delivers it to <base>/<date>/transactions_<timestamp>.<ext>. It's an incremental feed: with Receive new and updated transactions only enabled, each delivery includes just the rows that changed since the last one.

Columns​

New integrations start with a default selection of the most commonly used columns; you can adjust the selection from the column catalog in the dashboard. For step-by-step instructions, see Selecting columns for an export.

All dates and times are in UTC.

HeaderDescriptionTypeExample valueCan be null
rc_original_app_user_idCan be used as a unique Customer identifier to find all of a Customer's transactions.string$RCAnonymousID:87c6049c58069238dce29853916d624cNo
rc_last_seen_app_user_id_aliasCan be used together with rc_original_app_user_id to match transactions with Customer identifiers in your systems.string$RCAnonymousID:87c6049c58069238dce29853916d624cNo
countryStore country of a transaction when known, or an IP-based estimate of a Customer's country when not known.stringGBYes
country_sourcefrom_sdk when the store country of a transaction is known, or estimated when country is sourced from an IP-based estimate.stringfrom_sdkYes
product_identifierThe product identifier that was purchased. For Stripe, this can be a Stripe Price ID, Stripe Product ID, or manually specified identifier, depending on how the Product was imported or created. Learn how Stripe identifiers appear in exports.stringprice_1RzS9bQW6xN4cT2mY7kL8pA0No
product_display_nameThe display name of the product identifier if one has been setstringMonthly $9.99Yes
product_durationThe standard duration of the product if one is known by RevenueCat. May be null if RevenueCat does not know the authoritative duration.

product_duration does not represent the trial or introductory period length of a transaction, it only represents the standard duration of the product that's been subscribed to.
stringP1MYes
start_timePurchase time of transaction.datetime2023-01-01 08:27:06No
end_timeExpected expiration time of subscription. Null when is_auto_renewable = false
For Google Play, end_time can be before start_time to indicate an invalid transaction (e.g. billing issue).
datetime2023-02-01 08:27:06Yes
grace_period_end_timeExpiration time of a grace period (if applicable) for a subscription. Will remain set while a subscription is in its grace period, or if it exited its grace period without renewing. Null when a subscription is not in a grace period or expiration was not due to a grace period.datetime2023-02-17 08:27:06Yes
effective_end_timeSingle reference point of a Customer’s expiration and entitlement revocation; inclusive of each store’s logic for refunds, grace periods, etc.datetime2023-02-17 08:27:06Yes
storeThe source of the transaction. Can be app_store, play_store, stripe, or promotional.stringplay_storeNo
is_auto_renewabletrue for auto-renewable subscriptions, false otherwise.booleantrueNo
is_trial_periodtrue if the transaction was a trial.booleanfalseNo
is_in_intro_offer_periodtrue if the transaction is in an introductory offer period.booleanfalseNo
is_sandboxtrue for transactions made in a sandbox environment.booleanfalseNo
price_in_usdThe revenue (converted to USD) generated from the transaction after accounting for full and partial refunds. Can be null if product prices haven't been collected from the user's device.float0Yes
purchase_price_in_usdThe gross revenue (converted to USD) generated from the transaction. Remains set for refunded transactions. Can be null if product prices haven't been collected from the user's device.float9.99Yes
takehome_percentage[DEPRECATED] The estimated percentage of the transaction price that will be paid out to developers after commissions, but before VAT and DST taxes are taken into account. (will be either 0.7 or 0.85)

We recommend using tax_percentage and commission_percentage to calculate proceeds instead. Learn more here.
float0.7No
tax_percentageThe portion of a transaction’s price that will be deducted by the store for taxes. VAT and Digital Services Taxes may be withheld by stores depending on the store and country. To learn more about how RevenueCat estimates taxes, click here.float0.1442No
commission_percentageThe portion of a transaction's price that will be deducted by the store for commission. In stores where taxes are deducted before commission, this value will not equal the published commission from a store, because that commission is calculated on the post-tax revenue.float0.15No
store_transaction_idorderId or transaction_identifier.string123456789012345No
original_store_transaction_idorderId of first purchase or original_transaction_id. Can be used to find all related transactions for a single subscription.string011223344556677No
refunded_atWhen a refund was detected, null if none was detected. Is not set in the case of upgraded transactions for which the App Store issues a partial refund.datetime2023-02-20 05:47:55Yes
unsubscribe_detected_atWhen we detected an unsubscribe (opt-out of auto renew).datetime2023-02-16 14:17:10Yes
billing_issues_detected_atWhen we detected billing issues, null if none was detected.datetime2023-02-01 08:27:15Yes
purchased_currencyThe currency that was used for the transaction.stringGBPYes
price_in_purchased_currencyThe revenue (in the purchased currency) generated from the transaction after accounting for full and partial refunds. Can be null if product prices haven't been collected from the user's device.float0Yes
purchase_price_in_purchased_currencyThe gross revenue (in the purchased currency) generated from the transaction. Remains set for refunded transactions. Can be null if product prices haven't been collected from the user's device.float3.99Yes
entitlement_identifiersAn array of entitlements that the transaction unlocked or null if it didn't unlock any entitlements.string array["membership", "full_access"]Yes
renewal_numberAlways starts at 1. Trial conversions are counted as renewals. is_trial_conversion is used to signify whether a transaction was a trial conversion.integer2No
is_trial_conversionIf true, this transaction is a trial conversion.booleantrueNo
presented_offeringThe offering presented to users.stringDefault OfferingYes
ownership_typeWill be PURCHASED when a recorded transaction results from the Customer’s direct purchase of it, or FAMILY_SHARED when a recorded transaction results from the subscriber having received it through Family Sharing.

NOTE: The FAMILY_SHARED designation is only supported on App Store transactions.
stringPURCHASEDYes
reserved_subscriber_attributesThe reserved attributes set for the Customer. Keys begin with $.string JSON{"$ip": {"value": "203.78.120.117", "updated_at_ms": 1672549200}, "$gpsAdId": {"value": "80480bdc-06e0-11ee-be56-0242ac120002", "updated_at_ms": 1672549200}, "$androidId": {"value": "12345a9876b4c123", "updated_at_ms": 1673097132390}}Yes
custom_subscriber_attributesThe custom attributes set for the Customer.string JSON{"feature_setting": {"value": "1", "updated_at_ms": 1672549200}, "survey_response": {"value": "2", "updated_at_ms": 1599112814785}}Yes
platformLast seen platform of the Customer.stringandroidYes
updated_atThe last time an attribute of the transaction was modified.datetime2023-02-20 05:47:55No
offerThe offer that was used for a transaction (if applicable).stringblack_friday_discountYes
offer_typeThe type of offer that was used for a transaction (if applicable).stringoffer_codeYes
first_seen_timeThe time the customer was first seen by RevenueCat.datetime2023-01-01 03:00:00No
first_seen_or_purchased_timeEarliest of RevenueCat first seen, App Store install, first purchase, and Customer created_at. Matches Charts, Customer Profile, and the audience First seen or purchased filter. Not selected by default.datetime2023-01-01 03:00:00Yes
auto_resume_timeThe time when a Google Play subscription would resume after being paused.datetime2023-03-20 03:00:00Yes
cancel_survey_reasonGoogle Play cancel survey reason. One of Other reasons, Not enough usage, Technical issues, Cost, Found a better app, or Unspecified. Null for non-Google Play transactions. Not selected by default.stringTechnical issuesYes
enrolled_experiments_by_layerObject containing experiment enrollment information for the Customer, keyed by layer. Replaces the deprecated experiment_id and experiment_variant columns.string JSON{"layer_name": [{"experiment_id": "prexpaaaaaaaaaa", "variant": "a", "enrolled_at": "1970-01-01 00:00:00"}]}Yes
payment_objectsArray of external payments for the transaction. Each object has processed_at, currency, amount_in_currency, tax_in_currency, commission_in_currency, amount_in_usd, tax_in_usd, commission_in_usd, payment_identifier, source_identifier, country, and rc_public_id. Not selected by default.string JSON[{"processed_at": "2023-01-01 08:27:06", "currency": "GBP", "amount_in_currency": 3.99, "amount_in_usd": 4.8, "country": "GB"}]Yes

How do Stripe identifiers appear in exports?​

For a Stripe Product configured with a specific Price, product_identifier contains the exact Stripe Price ID (price_...). Stripe generates opaque Price IDs, so the value does not describe the Price's interval, amount, or parent Product. It does not contain the parent Stripe Product ID (prod_...), and RevenueCat does not add a separate column for the parent Product ID.

The identifier remains consistent with the Price attributed to the transaction throughout its lifecycle:

Transaction stateproduct_identifier
Initial purchase using a price-specific ProductPurchased Stripe Price ID
Renewal using the same PriceSame Stripe Price ID
Transaction updated after a Price changeNew Stripe Price ID
Cancellation or refundStripe Price ID already attributed to the transaction; the cancellation or refund fields change instead

Expect mixed Stripe identifiers in the same export:

  • prod_...: Stripe Products imported before RevenueCat supported Price-level imports are identified by their parent Stripe Product ID. Their transactions export prod_... in product_identifier.
  • Manually specified: Stripe Products created manually in RevenueCat are identified by the product_identifier value specified when they were created. Their transactions export that exact value.
  • price_...: Price-specific Stripe Products use the Stripe Price ID and export price_....

RevenueCat doesn't migrate existing mappings automatically, so the same export can contain prod_..., price_..., and manually specified identifiers.

If your export pipeline validates or joins product_identifier, update it to accept every identifier format configured in your project. To group price-specific transactions by their parent Stripe Product, resolve each Price ID through your Stripe product catalog. For manually created Products, use the configured product_identifier in your mapping. Use product_display_name when you need the display name configured in RevenueCat.

A Price change can cause an existing transaction to be exported again with an updated product_identifier. Follow the guidance in Handling updated transactions correctly when processing incremental deliveries.

Known quirks in transaction data​

All transaction data is based on the store receipts that RevenueCat has received. Receipts often have inconsistencies and quirks that you may need to account for. For example:

  • The expiration date of a purchase can be before the purchase date. This is Google's way of invalidating a transaction, for example when Google is unable to bill a Customer some time after a subscription renews. This doesn't occur on iOS.
  • If you migrated to RevenueCat, Google subscriptions that were expired for more than 60 days before being migrated won't have transaction histories in export files.
  • Apple and Google don't always provide the transaction price directly, so we rely on historical data and store APIs. This may result in inaccuracies if receipts were imported, or if a product price was increased before your App Store Connect API Key was added.
  • Renewal numbers start at 1, even for trials. Trial conversions increase the renewal number.
  • Data is pulled from a snapshot of the current receipt state. This means the same transaction can differ from one delivery to another if something changed (for example, due to a refund or billing issue). Recompute metrics for past time periods periodically to account for these changes, and use the updated_at field to detect whether a transaction changed since a prior export.
  • Data is current as of when the export begins generating. Changes that occur between the start of generation and delivery aren't reflected in that export.

We try to normalize or at least annotate these quirks as much as possible, but by and large we consider receipts the source of truth, so any inconsistencies in the transaction data can always be traced back to the receipt.

Handling updated transactions correctly​

Keep Receive new and updated transactions only enabled to significantly reduce the amount of data you need to process in each daily export.

The Receive new and updated transactions only option in the integration settings

Handling transaction updates can be tricky. These tips make it easier:

  1. For most stores, store_transaction_id will be unique for each transaction, but for Stripe it is not; so for best results we recommend treating every unique set of [store_transaction_id + renewal_number] as a unique transaction.
  2. Instead of overwriting prior transaction states when receiving an updated transaction, consider adding them as new rows to your output table and setting a property like is_latest to ensure you're never double-counting different versions of the same transaction. Or, you could set an ingested_time property to order the transactions by the most recent version you received from RevenueCat.
  3. When in doubt, use updated_at (provided by RevenueCat in your export) as a reference point to determine the latest version of a transaction if you have multiple prior versions and can't otherwise confidently determine which one is latest.

Sample queries for RevenueCat measures​

These sample queries use the Transactions feed. They're written in PostgreSQL and serve as starting points for reproducing common RevenueCat measures.

Sample queries for customized measures​

These examples also use the Transactions feed. Use them as ideas for adding your own customizations on top of the core measures provided by RevenueCat. They're written in PostgreSQL.

Was this page helpful?