Notifications & Events

How the Stay22 notification lifecycle works, how to customize the copy, and how to observe every decision the SDK makes.

The notification lifecycle

Once you set a travel context, the notification moves through a simple lifecycle — and the SDK tells you about every step through events:

  1. Scheduled. The SDK accepts the trip and queues one notification for it. A newer travel context replaces the pending one.
  2. Delivered. After the user leaves your app and the Stay22-managed delay elapses, the notification is shown.
  3. Opened. The user taps it and lands in a curated booking experience for their destination and dates, attributed to your aid.
  4. Skipped or cancelled. If conditions aren't right — the user is still in the app, the destination is their home city, a cooldown is active, permission is missing — the SDK holds back and emits the reason instead.

The delivery delay and frequency caps are configured by Stay22 for your account, not in code — that keeps timing tuned without app releases. Contact your Stay22 account manager or support@stay22.com to adjust them.

Customize the notification

The default copy works out of the box, but you can match it to your app's voice with notificationConfig. Set it once, any time after initialization:

import com.stay22.sdk.NotificationConfig
import com.stay22.sdk.NotificationInterruptionLevel

Stay22.notificationConfig = NotificationConfig(
    title = "Hotels in {destination}",
    subtitle = "{checkin} - {checkout}",
    message = "Tap to find the best deals nearby",
    categoryId = "stay22_destinations",
    threadId = "stay22",
    interruptionLevel = NotificationInterruptionLevel.active
)
Stay22.notificationConfig = NotificationConfig(
    title: "Hotels in {destination}",
    subtitle: "{checkin}-{checkout}",
    message: "Find stays near {destination}.",
    categoryId: "stay22_destinations",
    threadId: "stay22",
    badge: 1,
    interruptionLevel: .active,
    relevanceScore: 0.8
)

iOS additionally supports launchImageName, targetContentIdentifier, image attachments (local files), and custom actions buttons — see the iOS README for the full shape.

Text placeholders

Title, subtitle, and message support placeholders that the SDK fills in from the travel context:

PlaceholderReplaced with
{destination}The trip's destination name
{checkin}Check-in date, when provided
{checkout}Check-out date, when provided

Attribution

Bookings are attributed to the aid you initialized with. To segment where conversions come from — much like campaign on Allez links — set an optional campaign ID:

Stay22.campaignId = "spring_push"
Stay22.campaignId = "spring_push"

Events

The SDK emits an event for every decision it makes, which is the best window into what it's doing — wire them into your analytics or logging:

import com.stay22.sdk.Stay22Event

Stay22.advanced.setEventHandler { event ->
    when (event) {
        is Stay22Event.NotificationScheduled -> log("scheduled: ${event.destination}")
        is Stay22Event.NotificationClicked -> log("clicked: ${event.destination}")
        is Stay22Event.NotificationSkipped -> log("skipped: ${event.reason}")
        else -> Unit
    }
}
Stay22.advanced.setEventHandler { event in
    switch event {
    case .notificationScheduled(let destination, let delay):
        print("scheduled: \(destination), delay=\(delay)s")
    case .notificationClicked(let destination, let url):
        print("clicked: \(destination), \(url)")
    case .notificationSkipped(let reason):
        print("skipped: \(reason)")
    default:
        break
    }
}
EventFires when
locationUpdatedA travel context was accepted
notificationScheduledA notification was queued (destination, delay)
notificationShownThe notification was delivered
notificationClickedThe user tapped it (destination, booking URL)
notificationSkippedScheduling was skipped (reason)
notificationBlockedDelivery was blocked (reason)
notificationCancelledA pending notification was cancelled (reason)
enabledChangedStay22.isEnabled was toggled
travelContextClearedThe travel context was cleared

Skip and block reasons

When the SDK holds back, the reason tells you why — most are expected behavior, not errors:

ReasonMeaning
sdk_disabledStay22.isEnabled is false
missing_destinationThe travel context has no usable destination
app_foregroundThe user was still in your app at delivery time
home_destinationThe destination looks like the user's home area
partner_config_disabledScheduling is currently disabled for your account
local_cooldownA notification was shown too recently (frequency cap)
duplicate_pending_notificationA notification for this trip is already pending
notification_permission_deniedThe user hasn't granted notification permission

Manual scheduling

By default the SDK schedules automatically when a travel context is set. If you want to control the trigger yourself — for example, only after checkout of a flight purchase — run the same pipeline manually:

Stay22.advanced.scheduleNotification()
Stay22.advanced.scheduleNotification()

All the same relevance checks still apply — manual scheduling triggers the pipeline, it doesn't bypass it.

On this page