In-App Takeover setup

Integrate the EngageApp SDK (Web or Flutter) so full-screen in-app takeover campaigns render natively when your users open your app.

An In-App Takeover is a full-screen message rendered natively by our SDK when your end-user next opens your app. Delivery is pull-based: when a campaign targets a contact, the takeover is queued; the SDK fetches and renders it on the next app foreground, then reports impressions, clicks, and dismissals back to EngageApp automatically.

1. Activate the channel

Go to Channels → New → In-App Takeover. This is a platform-managed channel — there are no provider credentials. You can tune the display rules:

  • Default lifetime (hours) — how long a takeover stays fetchable before it expires unshown.
  • Max per app open — how many takeovers the SDK shows per foreground.
  • Min gap between (seconds) — minimum spacing between takeovers.

2. Create a publishable key

Under Settings → API keys, choose Publishable (pk_) and click Create. Copy the pk_… value — it is shown only once. Publishable keys are safe to embed in your app and are sent by the SDK as the X-Publishable-Key header.

3. Author a takeover template

Go to Templates → New → In-App Takeover. Set a headline, body, optional image or video, theme colors, and call-to-action buttons. Each button has an action: deeplink (route within your app), url (open a web URL), or dismiss. Use {{ first_name }} and other variables — they are resolved per contact at send time. Then send it from a campaign's send message step using your In-App channel.

Full-screen video

Set the media block to Video and paste an MP4 (H.264) URL. Video defaults to Full screen: it fills the takeover edge-to-edge with your copy and buttons laid over it behind a readability scrim. Switch the placement to Banner at top to keep the classic hero layout instead — the same two placements work for images.

  • Poster image — the still frame shown before playback starts, and the fallback when video can't play. Required if you turn autoplay off.
  • Autoplay / Loop / Muted — all on by default. Browsers and mobile OSes block autoplay with sound, so an unmuted video waits for a tap; the Web SDK retries muted and then shows playback controls.

On Flutter, playback is opt-in so the SDK stays dependency-free: pass a videoBuilder to EngageInAppConfig (see step 5). Without one, video takeovers render the poster image.

4. Integrate the Web SDK

Install @engageapp/inapp-web:

npm install @engageapp/inapp-web

Initialise, register handlers, identify your user, and fetch on every app foreground:

import { EngageInApp } from '@engageapp/inapp-web';

// 1. Initialise with your PUBLISHABLE key (safe in client code).
EngageInApp.init({
  publishableKey: 'pk_your_key'
  // apiBaseUrl defaults to https://api.engageapp.xyz
});

// 2. Handle button taps (deep links / URLs) in your app.
EngageInApp.setHandlers({
  onButtonTap: (action) => {
    if (action.type === 'deeplink') router.go(action.value);
    if (action.type === 'url') window.location.assign(action.value);
  }
});

// 3. Identify the signed-in user by your own id.
await EngageInApp.identify('user-123', { firstName: 'Ada' });

// 4. Fetch + render the next pending takeover. Call on every
//    app foreground / route into the app.
await EngageInApp.onAppForeground();

The SDK renders the takeover as a full-screen overlay and reports the impression, clicks, and dismissal for you. See the Web SDK reference for the full API.

5. Integrate the Flutter SDK

Add engageapp_inapp:

flutter pub add engageapp_inapp

Attach a navigator key so the SDK can present the takeover without a BuildContext, then initialise and identify:

final navigatorKey = GlobalKey<NavigatorState>();

void main() {
  EngageInApp.instance.init(EngageInAppConfig(
    publishableKey: 'pk_your_key',
    navigatorKey: navigatorKey,
  ));
  EngageInApp.instance.setHandlers(
    onButtonTap: (action, _) {
      if (action.type == 'deeplink') myRouter.go(action.value);
    },
  );
  runApp(MyApp(navigatorKey: navigatorKey));
}

// Attach the same key to your MaterialApp:
// MaterialApp(navigatorKey: navigatorKey, home: HomeScreen());

// After sign-in:
await EngageInApp.instance.identify('user-123', firstName: 'Ada');

Call onAppForeground() whenever your app resumes — for example from a WidgetsBindingObserver:

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  if (state == AppLifecycleState.resumed) {
    EngageInApp.instance.onAppForeground();
  }
}

To play full-screen video takeovers, hand the SDK a player. It ships without a video dependency, so you choose the package (for example video_player) and the SDK renders whatever widget you return:

EngageInApp.instance.init(EngageInAppConfig(
  publishableKey: 'pk_your_key',
  navigatorKey: navigatorKey,
  videoBuilder: (context, media) => MyTakeoverVideo(
    url: media.url,
    autoplay: media.autoplay,
    loop: media.loop,
    muted: media.muted,
  ),
));

The widget is laid out to fill its slot, so size it to cover (e.g. wrap the player in a FittedBox with BoxFit.cover). Without a videoBuilder, video takeovers render media.posterUrl instead — text, buttons, and reporting all still work.

Requires Flutter 3.27+. See the Flutter SDK reference for the full API.

6. (Optional) Identity verification

A publishable key authenticates your app, not the specific user. To stop a malicious client from passing another user's external_id, enable Settings → In-app identity verification. Your backend computes an HMAC digest and the SDK sends it as the X-Identity-Hash header:

// On your server (never ship the secret to the client):
//   hash = hmacSha256Hex(secret, externalId)

// Web:
await EngageInApp.identify('user-123', { identityHash: serverComputedHash });

// Flutter:
await EngageInApp.instance.identify('user-123', identityHash: serverComputedHash);

When verification is required, requests with a missing or invalid hash are rejected.

7. Test & troubleshoot

  • Send yourself a campaign through the In-App channel, then open your app and call onAppForeground().
  • Nothing shows? Confirm you called identify() first, that a takeover isn't already on screen, and that a fetch isn't already in flight — onAppForeground() no-ops in those cases.
  • Takeovers expire after the channel's lifetime if never fetched — reduce or extend it under the channel's display rules.
  • Delivered / shown / clicked events appear in your campaign analytics.