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.
| Requirement | Detail |
|---|---|
| Optimove credentials | Required for all platforms |
| Mobile SDK credentials | Required for all platforms — Overlay Messaging cannot be initialized without them |
| Both Web and Mobile SDKs integrated | Full cross-platform delivery requires both; partial SDK implementation limits reach to a single surface |
| Overlay Messaging enabled on tenant | Beta — provisioned by your CSE. Contact them to enable. |
| Domain and brand mapping | Domains and brands must be mapped to your tenant before messages can be delivered |
| Attribute webhook mapping for Overlay Messaging | Done 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 length | 1 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 messagedefer— leave the message intact; it can be fetched again in the same or a future sessiondiscard— 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
linkActionand 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 defaultsNote: Loading the SDK from a
<script>tag rather than a bundler? The globalwindow.optimoveSDKis the same instance, sowindow.optimoveSDK.API.overlayMessaging.setActionHandler({ … })works identically.
Reference
setActionHandler
setActionHandlersetActionHandler(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
OverlayActionHandlersOne 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;
}| Method | Payload | Meaning | SDK default |
|---|---|---|---|
linkAction | LinkActionPayload | A 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
LinkActionPayloadinterface LinkActionPayload {
url: string; // resolved to an absolute URL by the SDK
}OverlayMessage
OverlayMessageinterface 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
messageandpayload.
Behavior
- Overriding an action disables the default for that action — you own all routing for it. With
linkActionoverridden, 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
setActionHandlercall replaces your overrides. It re-merges over the SDK defaults, not over your previous call. Passundefinedto 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
linkActionto 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
reportEventcalls 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:
| Response | Effect |
|---|---|
show | Message is displayed to the customer. |
defer | Message is left intact and can be fetched again in the same or next session. Does not delete the message. |
discard | Message 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:
| Metric | Trigger | User-facing |
|---|---|---|
| Delivered | Message fetched by device. May fire more than once if fetched across multiple devices before deletion. | ✓ |
| Opened | Message displayed to the customer. | ✓ |
| Clicked | Customer clicked a CTA button. | ✓ |
| Closed | Customer dismissed the message via the close action. | ✓ |
| Processed | Message 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.
Updated 15 days ago