User Properties
Attach custom key-value metadata to users — subscription status, revenue, plan tier, and more.
User properties are custom key-value pairs stored on each user in Owlmetry. They let you see at a glance who is a paid subscriber, what plan they're on, how much they've spent, or any other user-level attribute you care about.
Properties are stored on each project-level user and displayed in the Users list in the dashboard. Users are unique per project — the same user ID seen from multiple apps (e.g., iOS and backend) is a single user with one set of properties.
How It Works
Properties are merged, not replaced. When you set { plan: "premium" }, it merges with existing properties. Setting a value to an empty string "" deletes that key.
Existing: { plan: "free", org: "acme" }
Set: { plan: "premium", role: "admin" }
Result: { plan: "premium", org: "acme", role: "admin" }Setting Properties
From the SDK
Both the Swift and Node SDKs provide setUserProperties():
// Swift
Owl.setUserProperties(["plan": "premium", "org": "acme"])// Node.js
const owl = Owl.withUser("user_123");
owl.setUserProperties({ plan: "premium", org: "acme" });From Integrations
Third-party integrations like RevenueCat automatically sync subscription data into user properties via webhooks. See Integrations for setup.
Via the API
POST /v1/identity/properties
Authorization: Bearer owl_client_...
{
"user_id": "user_123",
"properties": {
"plan": "premium",
"org": "acme"
}
}Limits
| Limit | Value |
|---|---|
| Max properties per user | 50 |
| Max key length | 50 characters |
| Max value length | 200 characters |
| Value type | Strings only |
Properties vs Event Attributes
Use user properties for data that describes the user and changes infrequently (plan tier, subscription status, company name). Use event custom attributes for data specific to a single event (button clicked, page URL, error code).
| User Properties | Event Attributes | |
|---|---|---|
| Scope | User-level | Event-level |
| Persistence | Stored on the user, visible in Users list | Stored on each event |
| Updates | Merged on each call | Immutable after ingest |
| Use case | Subscription status, plan tier | Action details, error context |
Identity Claim Merge
When an anonymous user is claimed (Swift Owl.setUser(), or direct POST /v1/identity/claim), properties from the anonymous user are merged into the real user. The real user's existing values take precedence on key conflicts.
Reserved Namespaces
Some property keys are populated by Owlmetry itself and should not be written manually from your app code. Overwriting them is allowed (properties are just strings), but you'll collide with the values integrations and attribution write.
| Prefix / Key | Populated by | Purpose |
|---|---|---|
rc_* | RevenueCat integration | Subscription status, plan, entitlements, billing period |
attribution_source | Attribution subsystem | Which network attributed the install (apple_search_ads, none, apple_test_install). The apple_test_install value is set with no asa_* fields when Apple returns its non-production fixture — TestFlight, Xcode-deployed dev builds, or the simulator. |
asa_*_id / asa_claim_type | Swift SDK (auto-captured) | Apple Search Ads IDs (campaign, ad group, keyword, ad, creative set) plus claim type, resolved from Apple's AdServices API |
asa_campaign_name, asa_ad_group_name, asa_ad_name, asa_keyword | Apple Search Ads integration (primary) or RevenueCat integration (secondary) | Human-readable names for the same campaign / ad group / ad the asa_*_id fields identify, plus the literal search keyword. Apple's AdServices Attribution API returns only numeric IDs; both sources resolve those IDs → names via Apple's ASA Campaign Management API (ASA integration does it directly; RC does it server-side and surfaces resolved names as subscriber attributes). Both cover every attributed user, subscriber or not. |
Attribution properties are captured once per anonymous user by the Swift SDK and carried through the claim merge into the real user. To opt out, set attributionEnabled: false on Owl.configure(). See Attribution for the full pipeline.
Backfill paths (RC sync/webhook and ASA integration sync) are additive: they never overwrite an asa_*_id or attribution_source set by the Swift SDK's live flow — they only fill empty slots. For users who predate the Apple AdServices capture, these backfill paths are the only source of attribution.
Dashboard columns
Attribution fields (asa_campaign_name, asa_ad_group_name, asa_keyword, asa_ad_name) are exposed as opt-in columns on the Users page via the column picker. Open the picker, toggle any attribution column on, drag to reorder — your configuration is persisted per-user via users.preferences so it follows you across browsers and devices. The same picker works for the Events page, letting you build personal views that stay out of your teammates' way.
