The in-app channel model
The in-app channel is Orbit’s owned engagement surface: content cards (a persistent, feed-style list) and in-app messages (transient modal, slide-up, fullscreen, or HTML overlays) that render inside your own website or app. It sits alongside SMS, WhatsApp, and email in the Messages hub, and it is the one channel that asks nothing of a carrier — this page explains the model behind it. The in-app channel guide is the operational how-to with copy-pasteable requests; the schema details below are deliberately not duplicated here.Section 1 — Why in-app is zero-carrier-cost
Every carrier channel pays a per-message leg: an SMS exits through a gateway, a WhatsApp template exits through Meta, an email exits through a provider. The in-app channel has no carrier leg at all, because delivery on this channel is a pull, not a push:- Your app fetches the visitor’s eligible feed from
GET /sdk/in-app/feedand renders it with its own components. - Engagement (impressions, clicks, dismissals) is reported back by the SDK in one lightweight
POST /sdk/in-app/eventscall — the same traffic your app already pays for.
Section 2 — Two render classes
The channel splits into two render classes with deliberately different lifetimes. Both are authored on the Messages → In-app console page (or throughPUT /api/v1/messages/in-app), and both travel over the same feed.
Content cards — persistent, feed-style. A card is a durable surface that lives in the visitor’s feed until it expires or is dismissed. Three families exist: classic (title-led), banner (image-only, the whole card is the tap target), and captioned_image (image plus title). pinned: true floats a card to the top of the feed; dismissible controls whether the visitor can remove it. Cards are the “inbox that never pushes” surface — a promotion, an announcement, an order status that should stay visible across sessions.
In-app messages — transient overlay. A message renders once as a modal, slideup, fullscreen, or html overlay and then leaves the feed. Buttons (capped at five; actions close, url, or deeplink) carry the call to action, and a numeric priority decides which message wins when several are eligible — the SDK shows one at a time. Messages are the “interrupt politely” surface — a welcome back nudge, a VIP preview, a one-time announcement.
Both classes share expiresAt (ISO-8601), after which a surface drops out of the feed server-side, so a time-boxed campaign never needs a cleanup job. The authored set is capped at 100 cards and 100 messages per tenant — a real feed, not a firehose.
Section 3 — Targeting model
What a visitor sees is never a broadcast; it is decided per fetch by stacking eligibility checks. The primary targeting lever is segment-scoped delivery with the same Contacts/CDP vocabulary the carrier channels use: any card or message may carrysegmentLabels, and a labeled surface is served only to a visitor whose resolved primary segment (contact_scores.segment_label, the same signal website personalization uses) is on the list. An unlabeled surface is global.
Two more levers stack on top: expiry filters time-boxed surfaces on every fetch, and per-device suppression keeps a dismissed id out of that device’s feed. Because the visitor’s segment assignment is resolved on each GET /sdk/in-app/feed, a change to either side — a new label on a surface, or a fresh identify on the visitor — takes effect on the next fetch with no separate publish step.
Section 4 — Engagement events
Carrier channels close the delivery loop with DLRs — receipts handed back by each carrier. The in-app channel has no carriers to hand receipts back, so engagement is the receipt: the SDK reports three verbs per rendered surface, tracked client-side and aggregated server-side.impression— the surface was rendered to this visitor (an “open” for a message, a “view” for a card).click— the visitor tapped the surface or one of its buttons; button-level events carrybuttonId, so a two-button message splits cleanly.dismiss— the visitor closed the surface; the id also joins the per-device dismissed list.
Section 5 — SDK integration surface
Two shipped SDK surfaces render the feed, and they share one wire model so a surface authored once renders identically in both:- Web —
OrbitInAppfrom the web SDK. Fetches the visitor’s feed with your public API key, renders content cards into a container element you own (renderCards), and shows the highest-priority eligible message as an overlay (showMessage). Rendered nodes are tagged (orbit-inapp-card orbit-inapp-card--<type>) so your CSS takes over. - React Native —
OrbitInAppClientfrom the React Native SDK. Same feed and event model; returns raw{ cards, messages }data for your own components and reports events the same way.
identify the visitor) and a per-device dismissed list, so a dismissed surface never flashes back on the next fetch.
Coexistence with carrier channels. In the Messages hub, in-app composes with the carrier channels rather than competing with them: cascade fallback chains can prefer the free in-app surface for reachable visitors and escalate to SMS or email only for the rest — the cheap channel primary, the carrier leg as backstop. That is the same pairing discipline described in the guide’s worked examples, at the model level.
Where to go next
- In-app channel guide — the operational how-to: enable, author, target, render, and read engagement.
- Cross-channel fallback — pairing in-app with carrier hops.
- Push channel model — the device-registry push counterpart.
- Website personalization — the same segment signal used for targeting.