Skip to main content
Answer

Clarification on Webhooks and Error Handling for In-App Purchases (Non-Subscription)

  • November 3, 2025
  • 2 replies
  • 63 views

Forum|alt.badge.img

 

We are using RevenueCat only for non-subscription in-app purchases (e.g. consumables). We currently handle reward distribution exclusively via webhook events on our backend.

 

However, we’ve encountered some cases where a purchase fails (e.g. credit card limit exceeded or other payment method failures), but the frontend has no way to detect this and may incorrectly treat the purchase as successful.

 

We’d like to ask:

 

  1. For non-subscription products, is there any way on the client side (React Native SDK) to detect if a purchase has failed right after calling Purchases.purchasePackage()?

  2. What specific webhook events does RevenueCat send to indicate:

     

    • a successful non-subscription purchase?

    • a failed non-subscription purchase (e.g. payment declined, card limit, etc)?

 

Based on your answers, we plan to distribute in-app rewards only after receiving the successful purchase webhook.

 

Additionally, we would like to clarify the behavior for “Billing Issues” or “Pending” states.

If a purchase enters a pending state (e.g. due to payment issues) and is later resolved successfully,

 

  • will the webhook for purchase success be sent again?

 

If that is the case, we expect that we can simply listen to only the “purchase success” state to determine when to issue rewards.

 

Thank you!

Best answer by hussain

Hi,

Thanks for reaching out.

For the client-side check in React Native Purchases.purchasePackage(...) will resolve only when the store marks the transaction as purchased. If the purchase doesn’t complete, the promise rejects with a typed error you can inspect (e.g., PURCHASE_CANCELLED_ERROR). You can handle the rejection and use the error code to decide what to show to the user, but for consumables most teams still gate fulfillment on webhook events.

For webhooks, consumables / non-subscriptions emit the NON_RENEWING_PURCHASE event. f a non-subscription is refunded, you’ll receive a CANCELLATION event (refunds & cancellations share this type; you can key off cancel_reason if needed). There’s no separate “failed purchase” webhook because failed payments that never finalize don’t produce a validated transaction (and therefore no event). You can find more details about the different type of webhook events on our docs here: https://www.revenuecat.com/docs/integrations/webhooks/event-types-and-fields

A practical, production-ready pattern for consumables could be:

  • On the client, call purchasePackage. If it fails, show the error and stop; if it succeeds or is pending, still don’t grant anything client-side.

  • In your backend, listen for NON_RENEWING_PURCHASE. When received, grant the reward idempotently by transaction_id (and optionally product_id/app_user_id). This avoids double-granting on webhook retries.

  • If a consumable is later refunded, process CANCELLATION to claw back or adjust balances as your economy allows.

One more thing you might find useful is RevenueCat’s Virtual Currency feature. you can let RevenueCat manage balances and transaction history (including awarding via one-time purchases or even subscriptions that top-up currency on a schedule), which can simplify your fulfillment logic and reduce edge-case handling. You can learn more about it in our docs here: https://www.revenuecat.com/docs/offerings/virtual-currency

Hope this helps, let me know if you have any other questions.

Best,

Hussain

 

This post has been closed for comments

2 replies

Forum|alt.badge.img
  • Author
  • New Member
  • November 4, 2025

For reference, we are currently using react-native-purchases version 9.1.0.


hussain
RevenueCat Staff
Forum|alt.badge.img+6
  • RevenueCat Staff
  • Answer
  • November 10, 2025

Hi,

Thanks for reaching out.

For the client-side check in React Native Purchases.purchasePackage(...) will resolve only when the store marks the transaction as purchased. If the purchase doesn’t complete, the promise rejects with a typed error you can inspect (e.g., PURCHASE_CANCELLED_ERROR). You can handle the rejection and use the error code to decide what to show to the user, but for consumables most teams still gate fulfillment on webhook events.

For webhooks, consumables / non-subscriptions emit the NON_RENEWING_PURCHASE event. f a non-subscription is refunded, you’ll receive a CANCELLATION event (refunds & cancellations share this type; you can key off cancel_reason if needed). There’s no separate “failed purchase” webhook because failed payments that never finalize don’t produce a validated transaction (and therefore no event). You can find more details about the different type of webhook events on our docs here: https://www.revenuecat.com/docs/integrations/webhooks/event-types-and-fields

A practical, production-ready pattern for consumables could be:

  • On the client, call purchasePackage. If it fails, show the error and stop; if it succeeds or is pending, still don’t grant anything client-side.

  • In your backend, listen for NON_RENEWING_PURCHASE. When received, grant the reward idempotently by transaction_id (and optionally product_id/app_user_id). This avoids double-granting on webhook retries.

  • If a consumable is later refunded, process CANCELLATION to claw back or adjust balances as your economy allows.

One more thing you might find useful is RevenueCat’s Virtual Currency feature. you can let RevenueCat manage balances and transaction history (including awarding via one-time purchases or even subscriptions that top-up currency on a schedule), which can simplify your fulfillment logic and reduce edge-case handling. You can learn more about it in our docs here: https://www.revenuecat.com/docs/offerings/virtual-currency

Hope this helps, let me know if you have any other questions.

Best,

Hussain