Chat actions
Automations
Send a message in the chat when something happens in your app: an offer when someone closes your paywall, help when a purchase is cancelled, a check-in after onboarding or a rating request after a good moment. You build automations in the dashboard; your app reports the moment with one line.
Automations need Reception iOS SDK 1.5 or later or react-native-reception 1.3 or later. The website chat has no automations.
How automations work
Open Automations in the dashboard and start from a template or from scratch. Each automation has four steps:
| Step | What you choose |
|---|---|
| Trigger | The event that starts it, such as paywall_declined, and whether it runs once per person or every time the event happens. |
| Who | Everyone, people without a subscription or people with one (a one-time purchase in the App Store, such as lifetime access, counts as with one, because the App Store can’t tell lifetime access from other one-time purchases), plus up to four rules on your app’s metadata, and whether it also reaches people in an ongoing conversation. A subscription event already says who has one, so the choice it rules out is disabled: after subscription_expired nobody has a subscription, after subscription_started everyone does, while subscription_cancelled and billing_issue leave access as it was until the period ends. |
| Wait | Right away, after a delay, or at a local time on a later day, and whether the messages come with a push. |
| Action | What happens, in order, from a sender you choose: open the chat (right away only), send a message, send an offer from your paywall cards, ask for an App Store rating, or wait before the rest goes out. Add as many steps as the moment needs, up to eight, so one automation can open the chat, send an offer, and follow up with a message a day later. |
Once per person, the default, reaches a person’s device once; later events send nothing. Every time reaches them at each event, once their previous run of the automation is complete.
A Wait action pauses the flow: the actions before it go out together, and the rest follows when the wait is over, with a push if the automation sends one. The follow-up is checked again before it goes out: it stays unsent when the person replied in the meantime (unless Also during a conversation is on), no longer matches the rules, or its offer or rating can’t be sent, and the automation then ends for them.
An ongoing conversation isn’t interrupted. While a conversation had activity in the last 24 hours, the automation skips the person and reaches them at a later event. Turn on Also during a conversation in the Who step to send it anyway; the message then joins that conversation.
An offer is sent only while the device reports that paywall as available, and a rating request needs your App Store ID and skips devices already asked for a review, by a teammate or an automation. An automation whose offer or rating can’t be sent holds everything back and tries again at a later event, so nobody gets a message that introduces nothing.
The messages are added to the person’s chat, in order, as unread. The conversation reaches your inbox when they reply.
Your app sends only events an enabled automation listens for. Other events never leave the device. A listened event registers the device with Reception if launch registration has not succeeded yet.
Messages sent later arrive as unread, with one push if you choose one. Offers are promotional, so a push with an offer needs the person’s consent in your app’s permission flow (App Store Review Guideline 4.5.4).
Admins create, edit and switch automations; members see them read-only. Editing never sends a message again, and deleting keeps messages already sent.
Report an event
Call track where the moment happens, after configuring Reception:
Reception.shared.track("onboarding_completed")In React Native:
Reception.track('onboarding_completed');The Trigger step suggests paywall_declined, checkout_cancelled, onboarding_completed, app_open (the app comes to the foreground) and app_close (it goes to the background); any name works. The name must match the automation’s event exactly and use 1–64 letters, digits, underscores, dots or hyphens. track returns at once and never fails: offline, on a server error or when no automation applies, nothing happens.
Pass opensChat: true where the chat should open again for people who got the message, such as a closed paywall:
Reception.shared.track("paywall_declined", opensChat: true)In React Native:
Reception.track('paywall_declined', { opensChat: true });The chat opens with the automation’s message when it is sent, and again at later events for people who got it, so they find the message and their conversation again. People the automation doesn’t reach, because its rules exclude them or its message comes later or as a push, aren’t interrupted. The chat only opens while your app is active, the chat is closed and the answer arrives within 5 seconds, so it never shows up late.
The automation’s switch in the dashboard decides both: without an enabled automation for the event, the chat stays closed and nothing is sent. Apps notice a switched automation within 5 minutes.
Right after you create an automation, its overview shows the line to add to your app and a prompt for your coding agent; Add to app in the overview’s header brings it back any time.
Subscription states
Ten events need no code: trial_started, trial_converted, trial_cancelled, trial_expired, subscription_started, subscription_renewed, subscription_cancelled, billing_issue, subscription_expired and refunded. They arrive from your subscription provider, RevenueCat, Superwall or Adapty, once it is connected under Settings → your app → Integrations (the Trigger step offers them only then), and reach every registered device of the person, right away and whether or not the app is open. See Subscription providers for the setup and what each event means.
Closed paywalls and cancelled purchases
Two moments are worth answering right away: someone closes your paywall without buying, or cancels Apple’s purchase sheet. Report both with opensChat: true. The Close paywall and Abandon transaction templates start from these events.
| Moment | Event | Your paywall | The chat |
|---|---|---|---|
| Paywall closed without a purchase | paywall_declined | closes, or comes straight back | opens once the paywall is gone, or above the one that came back |
| Purchase sheet cancelled | checkout_cancelled | stays open | opens above the paywall |
When the message has an offer, tapping it opens that paywall through your paywall card setup. After a cancelled purchase, closing the chat returns the person to your paywall.
An existing exit offer
If your app already shows an offer when the paywall closes or a purchase is abandoned, keep the offer and move the moment into the chat: leave the offering or placement where it is configured, stop presenting it from code at that moment, report the event, and pick that offering as the automation’s offer. It must be among the paywalls your app registers with Reception. With RevenueCat, exit offers only appear through presentPaywall and presentPaywallIfNeeded; present PaywallView(offering:) directly to drop them. Or keep the offer as it is and don’t report that event.
Your own paywall
Report the close from the sheet’s onDismiss, so the chat opens after the paywall is gone, and the cancelled purchase where your code learns of it. The chat waits for the purchase sheet to close and for a paywall your app shows again to settle, up to five seconds:
import Reception
import StoreKit
import SwiftUI
struct UpgradeButton: View {
@State private var showsPaywall = false
@State private var purchased = false
var body: some View {
Button("Upgrade") {
purchased = false
showsPaywall = true
}
.sheet(isPresented: $showsPaywall, onDismiss: {
if !purchased { Reception.shared.track("paywall_declined", opensChat: true) }
}) {
PlansView(onPurchase: { purchased = true; showsPaywall = false })
}
}
}
// Where your paywall buys a product:
func buy(_ product: Product) async throws -> Bool {
switch try await product.purchase() {
case .success(let verification):
guard case .verified(let transaction) = verification else { return false }
await transaction.finish()
return true
case .userCancelled:
Reception.shared.track("checkout_cancelled", opensChat: true)
return false
default:
return false
}
}With RevenueCat purchases, Purchases.shared.purchase(package:) reports the cancelled purchase as userCancelled.
In React Native, call Reception.track('paywall_declined', { opensChat: true }) from the onDismiss of your paywall’s modal or sheet, and Reception.track('checkout_cancelled', { opensChat: true }) where your code learns of the cancelled purchase.
RevenueCat paywalls
.sheet(isPresented: $showsPaywall, onDismiss: {
if !purchased { Reception.shared.track("paywall_declined", opensChat: true) }
}) {
PaywallView()
.onPurchaseCompleted { _ in
purchased = true
showsPaywall = false
}
.onPurchaseCancelled { Reception.shared.track("checkout_cancelled", opensChat: true) }
}With presentPaywallIfNeeded, pass the same calls as purchaseCompleted, purchaseCancelled and onDismiss.
Superwall
Add these two methods to your SuperwallDelegate, or set one if your app has none:
import Reception
import SuperwallKit
final class PaywallEvents: SuperwallDelegate {
func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) {
if case .transactionAbandon = eventInfo.event {
Reception.shared.track("checkout_cancelled", opensChat: true)
}
}
func didDismissPaywall(withInfo paywallInfo: PaywallInfo) {
if paywallInfo.closeReason == .manualClose {
Reception.shared.track("paywall_declined", opensChat: true)
}
}
}
// After Superwall.configure:
Superwall.shared.delegate = PaywallEvents()Report the closed paywall from didDismissPaywall, not from Superwall’s paywallDecline event. Superwall sends paywallDecline while the paywall is still on screen, and closing it would take the chat with it. .manualClose means the close button or a swipe down; a purchase or a paywall that makes way for another reports a different reason. If a Superwall campaign answers the close with its own offer, or an abandoned checkout with its own paywall, one of the two should own that moment: either don’t report that event, or remove the campaign rule, keep the paywall and its placement, and attach it to the automation as its offer.
Paywalls that must stay
What follows a closed offer is your app’s decision. If your paywall must stay, for example at the end of your onboarding, show it again when it closes: the chat opens above it once it is back. Show it again too when an offer from the chat closes without a purchase. With Superwall, where special_offer is the offer’s placement and onboarding the paywall’s:
func didDismissPaywall(withInfo paywallInfo: PaywallInfo) {
guard paywallInfo.closeReason == .manualClose else { return }
if paywallInfo.presentedByPlacementWithName == "special_offer" {
Superwall.shared.register(placement: "onboarding")
} else {
Reception.shared.track("paywall_declined", opensChat: true)
}
}Results
Each automation opens on its overview: how many people got the message, then opened the offer or tapped Rate, replied, and bought within 7 days after it. A purchase after the message isn’t necessarily because of it. Scheduled lists messages still waiting for their time, and Skipped lists people who matched but haven’t received it yet, with the reason: the offer wasn’t available, they were already asked for a rating, or a conversation was ongoing.
Purchases and the Who step’s subscription choice count both the App Store state the SDK reports and your app’s subscription_status metadata, whichever says the person pays, so web purchases and granted access your app reports as active count too.