Generic paywalls don’t work for everyone. In a fitness app, users have diverse goals: muscle gain, cardio stamina, yoga balance. Why treat them all the same?
With Adapty and HealthKit, you can build personalized paywalls based on the user’s workout activity type, offering content and offers tailored to their actual fitness habits.
This guide walks you through how to:
- Detect a user’s workout type via HealthKit
- Store it as a custom attribute in Adapty
- Segment users in the Adapty dashboard
- Serve different paywalls to each workout segment dynamically in SwiftUI
Let’s get started.
What’s wrong with generic paywalls?
A running enthusiast might respond better to “speed endurance plans,” while a cyclist would benefit more from “road performance packs.” Hardcoding these conditions quickly becomes unmanageable. Firebase Remote Config helps, but lacks strong analytics, A/B testing, or easy paywall editing.
With Adapty, we offload this logic to the dashboard, allowing for seamless visual and logical updates without modifying app code.
How can I get user workout data from HealthKit?
In my own app StrongerMe, I implemented this setup to show targeted offers to runners, swimmers, cyclists, and walkers. This allows each type of athlete to see offers that resonate with their habits and goals. For “Other” we will show the common paywall.
What HealthKit permissions are required?
Before accessing workout data, add the required HealthKit permissions and entries in your Info.plist
:
<key>NSHealthShareUsageDescription</key>
<string>We use your activity data to personalize your fitness experience and offers.</string>
<key>NSHealthUpdateUsageDescription</key>
<string>We update your activity preferences based on your workouts.</string>
Find more info about the keys:
After that you can request the authorization in your app:
let healthStore = HKHealthStore()
let typesToRead: Set = [HKObjectType.workoutType()]
healthStore.requestAuthorization(toShare: [], read: typesToRead) { success, error in
// Handle result like store HealthKit flag in UserDefault
}
How can I detect the most recent workout?
We focus on four types for this example:
- running
- walking
- cycling
- swimming
- other
import HealthKit
func getMostRecentWorkoutType() async throws -> String? {
let store = HKHealthStore()
let workoutType = HKObjectType.workoutType()
let predicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: [])
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: workoutType, predicate: predicate, limit: 1, sortDescriptors: [sortDescriptor]) { _, samples, _ in
guard let workout = samples?.first as? HKWorkout else { return }
let workoutType = workout.workoutActivityType
let exerciseType: String
switch workoutType {
case .running: exerciseType = "running"
case .walking: exerciseType = "walking"
case .cycling: exerciseType = "cycling"
case .swimming: exerciseType = "swimming"
default: exerciseType = "other"
}
// Now update Adapty attribute
var builder = AdaptyProfileParameters.Builder()
try? builder = builder.with(customAttribute: exerciseType, forKey: "exercise_type")
try? await Adapty.updateProfile(params: builder.build())
}
store.execute(query)
return nil
}
How can I store workout data in Adapty?
Once you have the workout type string, pass it to Adapty as a custom attribute. Of course we need no know about this field before showing the Paywall. You can set the default type early on login or app open:
var builder = AdaptyProfileParameters.Builder()
try? builder = builder.with(customAttribute: "running", forKey: "exercise_type")
try? await Adapty.updateProfile(params: builder.build())
What are user attribute limits?
- Up to 30 custom attributes per user
- Key name: up to 30 characters (letters, numbers, _, -, or . allowed)
- Value: string or float, max 50 characters long
→ Check User Attributes in Adapty
Should you identify the user?
If your app supports accounts, call identify
after login:
try await Adapty.identify("user_id")
Keep in mind: If you’ve assigned any data to an anonymous user such as custom attributes or tracking parameters from third-party sources make sure to reassociate that data with the user once they are identified.
How can I create segments based on exercise type?
We now need a criteria – Segment – which will be linked to exercise_type
from HealthKit.
⚠️ Segments are available only on a paid Adapty subscription plan.
How do you create segments in the Adapty dashboard?
In the Adapty Dashboard:
- Go to Segments, click Create Segment and Create custom attribute.

- Create a Custom attribute. It’s better to populate the expected values so we can use it in Segment filtering later.

- Add info and filtering for our attribute

Create these segments:
- “Runners” →
Exercise Type == running
- “Riders” →
Exercise Type == cycling
- “Swimmers” →
Exercise Type == swimming
- “Walkers” →
Exercise Type == walking
- “Other” →
Exercise Type == other
Each segment can then be tied to unique audiences and personalized paywalls.
The list for me is looking like this:

How do you assign paywalls to segments?
Creating paywalls
Now we need paywalls to split among the segments. Let’s create a pack of them to match our exercises. The docs cover the whole creation process here.
![Adapty Paywalls dashboard displaying five exercise-specific paywalls: Monthly[Running], Monthly[Other], Monthly[Cycling], Monthly[Swimming], and Monthly[Walking], all showing $0 revenue, proceeds, and net proceeds with 0 purchases and trials](https://adapty.io/assets/uploads/2025/06/Creating-paywalls-1024x562.webp)
All of them have separate images, text to attract the specified audience. Isn’t that great to have content based on exercise?!

What are Audiences in Adapty?
An Audience in Adapty defines which users will see a specific version of a paywall. It is a dynamic group that can include one or more Segments based on custom attributes like exercise_type
.
For example, in the placement “main_subscription”:
- The “Running Paywall” audience includes the segment “Runners”
- The “Riders Paywall” audience includes “Riders”
→ Check more on Audiences in Adapty
Right now there is All users audience setup. This means our segmentation doesn’t split users right now. But not for long.

Let’s tap Edit placement and then Add audience:
![Adapty "Add audience" dialog showing "Runners" audience selection with "Run Paywall" option selected and "Monthly[Running]" paywall chosen from](https://adapty.io/assets/uploads/2025/06/Add-audience-adapty-733x1024.webp)
Need to select our Segment (Runners in example) and related Paywall. After that it will appear in our placements splitting.
![Adapty Main placement configuration showing two audiences: "Runners" audience (#1) assigned Monthly[Running] paywall started Jun 7 2025, and "All Users" audience (#2) assigned Monthly[Other] paywall, with options to change paywalls or run A/B tests for each audience](https://adapty.io/assets/uploads/2025/06/Select-Segment-and-related-paywall-1024x485.webp)
By repeating the same steps for each Segment and Paywall we will get smooth coverage of all exercises. Prioritize audiences if overlaps exist. And all this without app modification.
How do you fetch and display paywall in SwiftUI?
Now all that is left is to show our paywall.
let placementId = "main_placement"
let paywall = try await Adapty.getPaywall(placementId: placementId)
let config = try await AdaptyUI.getPaywallConfiguration(forPaywall: paywall)
.paywall(isPresented: $showPaywall, paywallConfiguration: config) { action in
if case .close = action {
showPaywall = false
}
// handle purchases if needed
}
Adapty will automatically serve the matching paywall based on exercise_type
segment.
How does this compare to Firebase Remote Config / Hardcoded logic?
Feature | Adapty | Firebase Remote Config | Hardcoded logic |
Visual paywall editor | ✅ | ❌ | ❌ |
A/B testing | ✅ | ❌ | ❌ |
Analytics | ✅ | ⚠️ Basic | ❌ |
Easy updates | ✅ | ⚠️ Config only | ❌ |
Segmentation | ✅ | ❌ | ❌ |
Purchase handling | ✅ | ❌ | ❌ |
Conclusion
Using exercise_type
from HealthKit as a custom attribute in Adapty allows you to:
- Personalize paywalls based on actual fitness behavior
- Avoid manual logic in code or Firebase config
- Adjust offers, creatives, and pricing per segment in the dashboard
- Iterate quickly with analytics and A/B testing
This approach results in better user experience and improved conversions with minimal developer effort.