Skip to main content

In-App Messaging (API Pull) — iOS Overview

Paylisher can deliver in-app messages to your iOS app in two independent ways:

Delivery modeTransportWhen the message reaches the device
APNs pushA silent data push through Apple / FirebaseServer-initiated; can reach the device even in the background.
API pull (this section)The SDK polls Engage over HTTPSThe SDK asks "is there anything for me?" and Engage answers with the currently-matching campaigns. No APNs dependency.

This section is about the API-pull mode — in-app messages that do not depend on push infrastructure, that appear while the user is actively using the app, and that you can target to specific screens. The behavior is identical to the Android track — same config, same triggers, same render pipeline.


The pipeline

                       ┌──────────────────────────── Engage ────────────────────────────┐
App is foreground │ resolves your project/source from the public sdkKey │
or a screen changes │ → matches LIVE, API-pull in-app campaigns for this person │
│ │ → applies audience / screen target / time window / de-dup │
▼ │ │
SDK POST ──────────┼──▶ {host}/v1/push/inapp/fetch (X-SDK-Key header) │
SDK ◀──────────────┼── { "messages": [ { "payload": { … } } ] } │
│ └─────────────────────────────────────────────────────────────────┘

shouldDisplay? (time window) → de-dup (1h per pushId) → queue

render (banner / modal / fullscreen / carousel / native) — skips excluded screens

POST {host}/v1/push/inapp/ack status = DELIVERED → later SEEN / CLICKED / DISMISSED

The SDK sends only a public sdkKey (by default your SDK apiKey). Engage reverse-resolves the team / project / source server-side, so the app never embeds those IDs.


The request

POST {host}/v1/push/inapp/fetch

{
"distinctId": "<current distinct id>",
"sdkKey": "<public sdk key>",
"platform": "ios",
"maxMessages": 1,
"target": "wallet"
}

Header: X-SDK-Key: <public sdk key>. A successful response is 201 with a messages array (possibly empty). Acknowledgements go to the sibling path …/inapp/ack.

Endpoint host

The default endpoint is derived from your SDK host as {host}/v1/push/inapp/fetch. In most deployments Engage lives on a different host and path prefix than analytics ingestion (for example https://api-eu.paylisher.com/engage/v1/push/inapp/fetch). In that case set fetchEndpoint explicitly — see Integration.


When does the SDK fetch? (default behavior)

With the default config (autoFetchOnForeground = true), the SDK fetches automatically on:

TriggerBehavior
App becomes active~2s after didBecomeActive
The user changes screen inside the appon viewDidAppear (debounced 15s)
ManualPaylisherSDK.shared.refreshEngageInAppMessages(target:)

The screen-change fetch lets a campaign published while the user is browsing appear after one navigation, without a background→foreground cycle. Both automatic triggers send target = nil; the manual call lets you pass the current screen — see Screen targeting.

SwiftUI

The screen-change fetch fires on viewDidAppear. In a pure SwiftUI app that stays under one host controller, navigation pushes and the initial appearance fire it, but plain TabView switches may not. For reliable per-screen behavior use a manual per-screen refreshEngageInAppMessages(target:) — see Scenarios.


What happens to a returned message

  1. Time windowcondition.displayTime (60s tolerance) and condition.expireDate gate whether it shows now.
  2. De-duplication — the same pushId is not shown twice within a 1-hour window; a campaign already delivered to a distinctId is not returned again.
  3. Queue & render — rendered on the main thread. If the current screen is in excludedActivities (default ["Splash"]), rendering is deferred to the next non-excluded screen.
  4. Delay — render is delayed by max(condition.delay minutes, displayTime − now).
  5. AcknowledgeDELIVERED on enqueue; SEEN / CLICKED / DISMISSED follow interaction.

Next steps