Download OpenAPI specification:Download
The External Purchases API enables you to track subscription status and revenue data from Payment Service Providers that RevenueCat doesn't support natively. Such examples of Payment Service Providers, referenced as External Sources, are Paddle, PayPal, Braintree, Adyen, Recurly, etc.
You can post subscription status and payment events to the External Purchases API to use RevenueCat as a single source of truth for providing access to your application across mobile and web and to consolidate revenue data and key subscription metrics. Customer history, Webhooks and Integration events are supported as well, but they have limitations during the Beta period.
The API expects data to be posted in a format defined in the API reference, meaning that you would need to convert or transform data from the External Source before posting it to the External Purchases API endpoint. RevenueCat accepts data posted to this endpoint as is, and relies on your timely requests to provide entitlement access.
If you have a mobile first business with a web presence that follows a similar monetization model and subscription lifecycle implementation typical to the app stores, then this API is the right choice for you to consolidate your stack.
You can add an External Source under Project > Apps > +New. All you need to provide is the App name. The External API Keys and RevenueCat App ID required to call the API are automatically generated after hitting Save Changes.


Authentication for the External Purchases API is achieved by setting the Authorization header with a valid API secret external key. You'll find two main types of API keys in your RevenueCat dashboard: public app-specific , secret and secret app-specific.
Authorization: Bearer YOUR_REVENUECAT_APP_SPECIFIC_SECRET_API_KEY
The API keys are automatically generated for each External Source whenever you add a new External Source on the Add New App screen in the Dashboard.
You can retrieve these API keys both on the Apps settings page and Project settings > API Keys page.

You can call the External Purchases API only with the corresponding app-specific Secret API key. The app-specific Secret API Key should be kept out of any publicly accessible areas such as GitHub or client side code. Additionally, an app-specific Public API Key is generated so public endpoints like Offerings can be accessed.
The API has one endpoint, POST/receipts/external.
POST/receipts/external is used for requests with a single purchase and/or payment, and processes them right away. Calling Get Customer right after will return the updated entitlements.
For more details check the API reference.
The body of the POST requests should be encoded in JSON and have the 'Content-Type' header set to 'application/json'.
Content-Type: application/json
{
"purchase": {
"object": "external_subscription",
"customer_id": "app_user_id12341234",
"source_subscription_identifier": "paddle_sub_id1234",
"source_product_identifier": "paddle_product_id1234",
"updated_at": "2023-04-01T00:00:00",
"current_period_starts_at": "2023-04-01T00:00:00",
"current_period_ends_at": "2023-05-01T00:00:00",
"gives_access": true,
"status": "active",
"environment": "production",
"auto_renewal_status": "will_renew"
},
"payment": {
"object": "external_subscription_payment",
"source_subscription_identifier": "paddle_sub_id1234",
"payment_identifier": "payment_id1234",
"processed_at": "2023-04-01T00:00:00",
"amount_in_local_currency": {
"gross": 9.99,
"currency": "USD"
}
}
}
This example of a subscription lifecycle demonstrates the series of API request to make to the External Purchases API and the resulting events generated (for Customer History, Webhooks and Integration Events).
This flow covers a Trial Flow with Successful Conversion, an ordinary Subscription Renewal, a Billing Issue Flow that gets resolved in Grace Period, and eventually a Cancellation Flow.
{
"purchase": {
"object": "external_subscription",
"customer_id": "app_user_id12341234",
"source_subscription_identifier": "paddle_sub_id1234",
"source_product_identifier": "paddle_product_id1234",
"updated_at": "2023-03-01T00:00:00",
"current_period_starts_at": "2023-03-01T00:00:00",
"current_period_ends_at": "2023-04-01T00:00:00",
"gives_access": true,
"status": "trialing",
"environment": "production"
},
"payment": null
}
{
"purchase": {
"object": "external_subscription",
"customer_id": "app_user_id12341234",
"source_subscription_identifier": "paddle_sub_id1234",
"source_product_identifier": "paddle_product_id1234",
"updated_at": "2023-04-01T00:00:00",
"current_period_starts_at": "2023-04-01T00:00:00",
"current_period_ends_at": "2023-05-01T00:00:00",
"gives_access": true,
"status": "active",
"environment": "production",
"auto_renewal_status": "will_renew"
},
"payment": {
"object": "external_subscription_payment",
"source_subscription_identifier": "paddle_sub_id1234",
"payment_identifier": "payment_id1234",
"processed_at": "2023-04-01T00:00:00",
"amount_in_local_currency": {
"gross": 9.99,
"currency": "USD"
}
}
}
{
"purchase": {
"object": "external_subscription",
"customer_id": "app_user_id12341234",
"source_subscription_identifier": "paddle_sub_id1234",
"source_product_identifier": "paddle_product_id1234",
"updated_at": "2023-05-01T00:00:00",
"current_period_starts_at": "2023-05-01T00:00:00",
"current_period_ends_at": "2023-06-01T00:00:00",
"gives_access": true,
"status": "active",
"environment": "production",
"auto_renewal_status": "will_renew"
},
"payment": {
"object": "external_subscription_payment",
"source_subscription_identifier": "paddle_sub_id1234",
"payment_identifier": "payment_id2345",
"processed_at": "2023-05-01T00:00:00",
"amount_in_local_currency": {
"gross": 9.99,
"currency": "USD"
}
}
}
{
"purchase": {
"object": "external_subscription",
"customer_id": "app_user_id12341234",
"source_subscription_identifier": "paddle_sub_id1234",
"source_product_identifier": "paddle_product_id1234",
"updated_at": "2023-06-01T00:00:00",
"current_period_starts_at": "2023-06-01T00:00:00",
"current_period_ends_at": "2023-06-14T00:00:00",
"gives_access": true,
"status": "in_grace_period",
"environment": "production",
"auto_renewal_status": "will_not_renew"
},
"payment": null
}
{
"purchase": {
"object": "external_subscription",
"customer_id": "app_user_id12341234",
"source_subscription_identifier": "paddle_sub_id1234",
"source_product_identifier": "paddle_product_id1234",
"updated_at": "2023-06-12T00:00:00",
"current_period_starts_at": "2023-06-01T00:00:00",
"current_period_ends_at": "2023-07-01T00:00:00",
"gives_access": true,
"status": "active",
"environment": "production",
"auto_renewal_status": "will_renew"
},
"payment": {
"object": "external_subscription_payment",
"source_subscription_identifier": "paddle_sub_id3456",
"payment_identifier": "payment_id1234",
"processed_at": "2023-06-12T00:00:00",
"amount_in_local_currency": {
"gross": 9.99,
"currency": "USD"
}
}
}
{
"purchase": {
"object": "external_subscription",
"customer_id": "app_user_id12341234",
"source_subscription_identifier": "paddle_sub_id1234",
"source_product_identifier": "paddle_product_id1234",
"updated_at": "2023-06-18T00:00:00",
"current_period_starts_at": "2023-06-01T00:00:00",
"current_period_ends_at": "2023-07-01T00:00:00",
"gives_access": true,
"status": "active",
"environment": "production",
"auto_renewal_status": "will_not_renew"
},
"payment": null
}
{
"purchase": {
"object": "external_subscription",
"customer_id": "app_user_id12341234",
"source_subscription_identifier": "paddle_sub_id1234",
"source_product_identifier": "paddle_product_id1234",
"updated_at": "2023-07-01T00:00:00",
"current_period_starts_at": "2023-06-01T00:00:00",
"current_period_ends_at": "2023-07-01T00:00:00",
"gives_access": false,
"status": "expired",
"environment": "production",
"auto_renewal_status": "will_not_renew"
},
"payment": null
}
Note: Make sure to include the
current_period_starts_atfor the transaction you are trying to expire. Otherwise your target transaction might not be found by the system.
If subscription statuses arrive out of order (eg. RevenueCat receives a previous subscription period before a subsequent subscription period), RevenueCat needs to make a decision about how the history of the subscription looked like. This section describes various scenarios that can occur.
We know that the new transaction ends before the latest old one.
The transaction can fit everywhere between or overlap the existing transactions. The new transaction can be in any of the following positions.

We need to find the previous and next overlapping transactions if any.
Both previous and next overlapping old transactions exist. As a result, we shrink the previous old transaction to end right before the new one. Similarly, we shrink the new transaction to end right before the next old one.

Only the previous overlapping old transaction exist. As a result, we shrink the prev transaction to end right before the new one. The new transaction is not changed.

Only the next overlapping old transaction exist. As a result, we shrink the new transaction to end right before the next old one.

There’s no old transaction overlapping with the new one. As a result, we just store the new transaction as is.

The new transaction overlaps completely a old one (prev). As a result, we send ERROR, the new transaction is skipped.

We know that the new transaction ends after the latest old one.
Old transaction starts before and ends during new one. As a result, we shrink the old transaction to end right before the new one

Old transaction starts and ends during new one. As a result, we send ERROR, the new transaction is skipped.

Old transaction starts and ends before the new one. As a result, the new transaction is saved, the old one is not modified.

The following limitations apply during Beta period:
purchase JSON with external_subscription object:customer_id field, meaning that we don't support Transferring purchases seen on multiple App User IDscurrent_period_ends_at was in the future, and it's changed to a different future date.auto_renew_status field: will_renew, will_not_renew, unknown value. We omit the will_change_product for now, meaning that we don't support Upgrades, Downgrade, Crossgrades and resulting event (PRODUCT_CHANGE event).INITIAL_PURCHASE / RENEWAL event)current_period_ends_at date, do not alter existing Events, which means that the Customer history won't reflect accurately the most up to date expiration_at_ms.source_subscription_identifier gets provided, doesn’t matter if it’s the same product, we can end up with concurrent transactionsEXPIRATION Events are not fired automatically. The developer needs to explicitly send a request to the External Purchases API endpoint when status changed to expired.ExternalPurchase (object) or ExternalSubscription (object) (Purchase) The current status of one external purchase to track | |||||||||||||||||||
Any of
| |||||||||||||||||||
ExternalSubscriptionPayment (object) or ExternalPurchasePayment (object) (Payment) The current status of one external payment to track | |||||||||||||||||||
Any of
| |||||||||||||||||||
{- "purchase": {
- "object": "external_purchase",
- "customer_id": "string",
- "source_purchase_identifier": "string",
- "source_product_identifier": "string",
- "updated_at": "2019-08-24T14:15:22Z",
- "status": "owned",
- "environment": "production",
- "metadata": { }
}, - "payment": {
- "object": "external_subscription_payment",
- "source_subscription_identifier": "string",
- "payment_identifier": "string",
- "processed_at": "2019-08-24T14:15:22Z",
- "country": "string",
- "amount_in_local_currency": {
- "currency": "string",
- "gross": 0,
- "tax": 0,
- "commission": 0
}, - "amount_in_usd": {
- "gross": 0,
- "tax": 0,
- "commission": 0
}, - "active_offer_type": "free_trial",
- "active_offer": "string"
}
}{- "purchase": "string",
- "payment": "string"
}