Getting Started with Overlay Messaging


Overlay Messaging enables your application to display personalized pop-up messages to customers across both web and mobile surfaces from a single Optimove campaign. This guide covers SDK integration for Android, iOS, and Web, along with a behavior reference for channel mechanics.

Beta: Overlay Messaging is currently in beta. Your CSE handles tenant enablement—contact them to obtain access.

Prerequisites

Overlay Messaging is a cross-platform channel — both the Web SDK and Mobile SDK must be implemented to deliver messages across all surfaces. If only one SDK is integrated, messages will only reach customers on that platform.

RequirementDetail
Optimove credentialsRequired for all platforms
Mobile SDK credentialsRequired for all platforms — Overlay Messaging cannot be initialized without them
Both Web and Mobile SDKs integratedFull cross-platform delivery requires both; partial SDK implementation limits reach to a single surface
Overlay Messaging enabled on tenantBeta — provisioned by your CSE. Contact them to enable.
Domain and brand mappingDomains and brands must be mapped to your tenant before messages can be delivered
Attribute webhook mapping for Overlay MessagingDone by your CSE - contact them to ensure everything’s set up correctly
Push permission (mobile, triggered only)Triggered overlays on mobile require the user to have granted push permission; your app must handle the permission prompt — not managed automatically by the SDK
Minimum session length1 hour (configurable upward)

Set up

Mobile SDKs

Android — Initialization

Enable Overlay Messaging when initializing the SDK by calling .enableOverlayMessaging(sessionLengthHours).

Integer sessionLengthHours = 1;

Optimove.initialize(this, OptimoveConfig.Builder(
        "<YOUR_OPTIMOVE_CREDENTIALS>",
        "<YOUR_OPTIMOBILE_CREDENTIALS>")
    // ...
    .enableOverlayMessaging(sessionLengthHours)
    .build());

Android — Session reset

Call this to reset the current session and allow the SDK to fetch the next available session message. Use when a new user session begins — for example, on logout/login.

OptimoveOverlayMessaging.getInstance().resetSession();

Android — Setting an interceptor

An interceptor lets you run custom logic before any overlay message is processed. The interceptor receives the message object and must return one of three outcomes:

  • show — display the message
  • defer — leave the message intact; it can be fetched again in the same or a future session
  • discard — permanently delete the message from the backend

If the interceptor does not resolve within the configured timeout (default: 5,000ms), it resolves automatically as defer.

OptimoveOverlayMessaging.getInstance()
    .setInterceptor(new OptimoveOverlayMessaging.OverlayMessagingInterceptor() {
        @Override
        public void onMessageLoaded(
            OverlayMessagingMessage message,
            OptimoveOverlayMessaging.OverlayMessagingInterceptorCallback callback
        ) {
            // Your logic here — call one of the following:
            callback.show();
            // callback.defer();
            // callback.discard();
        }

        @Override
        public long getTimeoutMs() {
            return 5000;
        }
    });

OverlayMessagingMessage object (Android):

public class OverlayMessagingMessage {
    public enum MessageType {
        SESSION, IMMEDIATE
    }
    public long getId() { return id; }
    public JSONObject getContent() { return content; }
    @Nullable
    public JSONObject getData() { return data; }
    public MessageType getType() { return type; }
}

iOS — Initialization

Optimove.initialize(with: OptimoveConfigBuilder(
        optimoveCredentials: "<YOUR_OPTIMOVE_CREDENTIALS>",
        optimobileCredentials: "<YOUR_OPTIMOBILE_CREDENTIALS>")
    // ...
    .enableOverlayMessaging(sessionLengthHours: 1)
    .build())

iOS — Session reset

OptimoveOverlayMessaging.resetSession()

iOS — Setting an interceptor

private class MyInterceptor: OverlayMessagingInterceptor {
    func onMessageLoaded(
        _ message: OverlayMessagingMessage,
        callback: OverlayMessagingInterceptorCallback
    ) {
        // Your logic here — call one of the following:
        callback.show()
        // callback.deferMessage()
        // callback.discard()
    }

    func getTimeoutMs() -> Int { return 5000 }
}

OptimoveOverlayMessaging.setInterceptor(MyInterceptor())

OverlayMessagingMessage object (iOS):

public struct OverlayMessagingMessage {
    public enum MessageType {
        case session
        case immediate
    }
    public let id: Int64
    public let content: NSDictionary
    public let data: NSDictionary?
    public let type: MessageType
}

Web SDK

Initialisation

No SDK code changes are required to enable Overlay Messaging on web. Contact your CSE to have it enabled for your tenant.

Session reset

Call this to reset the current session and allow the SDK to fetch the next available session message.

optimoveSDK.API.overlayMessaging.resetSession();

Note: Minimum session length is 1 hour.

Setting an interceptor

The web interceptor works identically to mobile — return 'show', 'defer', or 'discard'. If the callback does not resolve within the configured timeout (default: 5,000ms), it resolves as defer.

// Basic usage — show the message
optimoveSDK.API.overlayMessaging.setInterceptor(
    (message) => { return 'show'; },
    { timeoutMs: 10000 }
);

// Advanced usage — take full control of message presentation
optimoveSDK.API.overlayMessaging.setInterceptor(
    (message) => {
        customMessageQueue.push(message);
        return 'discard';
    },
    { timeoutMs: 10000 }
);

Relevant types:

type InterceptorResponse = 'show' | 'defer' | 'discard';

type InterceptorCallback = (message: OverlayMessage) =>
    InterceptorResponse | Promise<InterceptorResponse>;

interface InterceptorOptions {
    timeoutMs?: number;
}

interface OverlayMessage {
    id: number;
    content: { ver: number; html: string };
    data?: Record<string, unknown>;
    type: OverlayMessageType;
}

enum OverlayMessageType {
    Session = 'session',
    Immediate = 'immediate'
}

Setting overlay options

Change the session length at runtime. This takes effect immediately — if the new session length indicates the current session has already elapsed (counting from the previous session start), a new session begins.

optimoveSDK.API.overlayMessaging.setOverlayOptions({
    sessionLengthHours: 2
});

Relevant types:

interface OverlayOptions {
    sessionLengthHours?: number;
}

Action handler (deep linking)

When a customer taps a call-to-action (CTA) that carries a link, the SDK opens it for you by default — on the web, in a new browser tab (mailto:, tel:, and sms: links open via the device handler). Because the SDK can only open the link, it can't route inside your own application.

Supply an action handler to take that over. The SDK ships a default for every overlay action; you override only the actions you want to own. When your handler runs, the SDK passes it the message that was tapped and the action's typed payload, then steps aside so you can navigate however you like — for example, deep-linking with your own router or opening the URL in an in-app browser.

Note: The action handler is separate from the message interceptor (setInterceptor). The interceptor runs before a message is shown and decides whether to display it (show / defer / discard); the action handler decides what happens when a CTA is tapped. They are independent.

Quick start

// `optimoveSDK` is your initialized SDK instance (also available as the global `window.optimoveSDK`).
optimoveSDK.API.overlayMessaging.setActionHandler({
    linkAction(message, payload) {
        myRouter.navigate(payload.url); // deep-link in-app instead of a new tab
    }
});

Pass an object with one method per action type you want to own. The only action type today is linkAction. Any method you leave out keeps the SDK's built-in default automatically.

Register your handler once, at startup, before any message can be presented. Calling setActionHandler before SDK initialization finishes is fine — it still takes effect.

How it works

The SDK defines an action handler with one method per overlay action type, each with its own typed payload, and provides a default implementation for every method. You call setActionHandler with a partial object containing only the methods you want to override. The SDK dispatches against { ...defaults, ...yourOverrides }:

  • Override an action's method → on a matching tap your method receives (message, payload) and you decide what to do.
  • Don't override it → the SDK runs its default. With no overrides, the SDK opens links as it always has. Override linkAction and you own where the link goes — the SDK will not also open the URL. Any action you didn't override — including action types added in future SDK versions — keeps the SDK default. Overriding one action never affects another.

Each call replaces your whole override set

setActionHandler re-merges over the SDK defaults every time, so each call replaces the previous overrides rather than adding to them. To stop overriding an action, call setActionHandler again without that method. To restore every default at once, pass an empty object or undefined:

optimoveSDK.API.overlayMessaging.setActionHandler(undefined); // restore all SDK defaults

Note: Loading the SDK from a <script> tag rather than a bundler? The global window.optimoveSDK is the same instance, so window.optimoveSDK.API.overlayMessaging.setActionHandler({ … }) works identically.

Reference

setActionHandler

setActionHandler(overrides: Partial<OverlayActionHandlers> | undefined): void;

Registers your per-action overrides. Each call replaces the previous overrides; methods you omit fall back to the SDK default. Pass undefined to restore all defaults.

OverlayActionHandlers

One method per overlay action type, each with a typed payload. The SDK supplies a default for every method; you override the ones you want by passing them in a Partial. New action types are added here as new methods (each with a default), which is non-breaking for existing integrations.

interface OverlayActionHandlers {
    linkAction(message: OverlayMessage, payload: LinkActionPayload): void;
}
MethodPayloadMeaningSDK default
linkActionLinkActionPayloadA CTA that navigates (carries a URL).Opens the URL — a new tab, or location.href for mailto: / tel: / sms:.

linkAction is the only action type today. The set is extensible — new methods are added over time, and any method you don't override keeps the SDK default.

LinkActionPayload

interface LinkActionPayload {
    url: string; // resolved to an absolute URL by the SDK
}

OverlayMessage

interface OverlayMessage {
    id: number;
    content: { ver: number; html: string }; // opaque message HTML, rendered by Optimove
    data?: Record<string, unknown>;          // optional campaign metadata
    type: 'session' | 'immediate';           // what triggered the message
}

Note: These are the shapes the SDK uses internally. Mirror them in your own code (or let inference type the handler arguments) for editor completion on message and payload.

Behavior

  • Overriding an action disables the default for that action — you own all routing for it. With linkAction overridden, the SDK will not open the URL itself.
  • Methods you don't override — and future action types — keep the default. Overriding one action never affects another.
  • Each setActionHandler call replaces your overrides. It re-merges over the SDK defaults, not over your previous call. Pass undefined to go back to all defaults.
  • A failing handler does nothing (fail-closed). If your method throws, the SDK logs it and does not fall back to opening the URL. (If you overrode linkAction to allow-list or sanitize a URL, opening it anyway on a bug would defeat the point.)
  • Reporting and dismissal are unaffected. A handled tap is still reported to Optimove for campaign analytics, and the overlay still closes if the message is meant to close — regardless of whether, or how, you handled the action.

Recipes

Deep-link with your app's router

optimoveSDK.API.overlayMessaging.setActionHandler({
    linkAction(message, payload) {
        myRouter.navigate(payload.url);
    }
});

Allow-list or sanitize before navigating — because handlers fail closed, a rejected URL simply goes nowhere:

optimoveSDK.API.overlayMessaging.setActionHandler({
    linkAction(message, payload) {
        const url = new URL(payload.url);
        if (!ALLOWED_HOSTS.has(url.host)) {
            return; // not on the allow-list → don't navigate
        }
        window.open(url.href, '_blank', 'noopener,noreferrer'); // synchronous
    }
});

Attach your own analytics, using the message

optimoveSDK.API.overlayMessaging.setActionHandler({
    linkAction(message, payload) {
        analytics.track('overlay_cta_clicked', { messageId: message.id, ...message.data });
        myRouter.navigate(payload.url); // still synchronous
    }
});

Trigger setup for web and mobile

Because Overlay Messaging is cross-platform, triggers must be wired up on both surfaces for a triggered campaign to reach a customer on whichever device they're active on.

  • Web events are reported via the Web SDK using reportEvent calls on your site.
  • Mobile events are reported via the Mobile SDK on iOS and Android.

If a trigger event is implemented only on one platform, customers on the other platform will not receive triggered overlays — even if the channel is active on both platforms. Confirm with your CSE that all trigger events intended for Overlay Messaging campaigns are implemented and verified on both your web and mobile integrations before going live.


Behavior reference

Sessions

A session defines the window during which a customer is eligible to receive one scheduled overlay message. The session persists for the configured sessionLengthHours (default: 1 hour; minimum: 1 hour). A session starts when the SDK is first initialized and has never loaded before, when the configured session duration expires, or when resetSession() is called programmatically.

To programmatically start a new session — for example, on user logout — call resetSession() on the relevant SDK.

Message limits

  • Session (scheduled) messages: One per session restart. Only the most recent eligible scheduled message is shown.
  • Triggered (immediate) messages: Not limited to one per session. Multiple triggered messages may be delivered within a session — each shown after the previous is interacted with or dismissed — provided each message is still within its 2-minute TTL at the point of fetch.

Scheduled message delivery

At session start, the SDK syncs scheduled messages and identifies the most recent by execution time (send_at).

  • If the most recent message is within its TTL → it is shown.
  • If the most recent message has expired → nothing is shown. Older messages are not surfaced as a fallback.
  • Default TTL: 2 days from execution time. If the message is not fetched within 2 days of the campaign execution date and time, it expires and is not shown.
  • Maximum retention: 14 days.

Triggered message delivery

  • Triggered messages have a TTL of 2 minutes from the time they are sent.
  • Multiple triggered messages can be delivered within a session if the customer performs multiple trigger actions. Each message's TTL is independent.
  • On mobile, delivery requires push permission. Customers without push permission will not receive triggered overlays on mobile.
  • Delivery is near real-time: silent push on mobile, web sync on event emit.

Cross-device deduplication

When a message is interacted with or dismissed, it is hard-deleted from the backend. This mechanism prevents the message from reappearing on other devices. State syncs at session start and at a minimum once per hour on mobile.

Edge case: If a customer has the application open on two devices simultaneously at the point of delivery, both may briefly display the message before the deletion sync propagates.

Interceptor behavior

The interceptor is called before any message is presented. Outcomes:

ResponseEffect
showMessage is displayed to the customer.
deferMessage is left intact and can be fetched again in the same or next session. Does not delete the message.
discardMessage is permanently deleted from the backend. Will not appear on any device.

If the interceptor timeout elapses without a response, the outcome defaults to defer.

Default timeout: 5,000ms. Configurable via getTimeoutMs() (mobile) or the timeoutMs option (web).

Metrics

The following metrics are tracked and reported in Mission Control:

MetricTriggerUser-facing
DeliveredMessage fetched by device. May fire more than once if fetched across multiple devices before deletion.
OpenedMessage displayed to the customer.
ClickedCustomer clicked a CTA button.
ClosedCustomer dismissed the message via the close action.
ProcessedMessage written to storage.Internal only

Rendering

  • Web and mobile use a shared renderer.
  • On the web, the overlay renders inside a Shadow DOM — CSS is fully isolated from the host page in both directions.

Did this page help you?