Playbooks
Serving Moments in Mobile Apps
Swift - Moments API Integration Guide
16 min
overview the moments api docid\ duaxqa4vbbpvihg4cqaac allows you to display personalized, performance driven offers to users directly within your ios application this guide outlines the steps required to integrate the api into your appβfrom fetching offers to rendering ui and tracking user interactions by following this integration guide, youβll be able to fetch real time, targeted offers based on user context present these offers using your own custom ui or prebuilt reference components track user responses and impressions for reporting and optimization to explore a working example, see the momentscience ios example app on github prerequisites before you begin the integration process, ensure the following requirements are met api key before you start the integration, you must acquire a unique api key follow the instructions provided here to obtain your key minimum ios version your project must target ios 15 0 or higher integration steps the moments api enables your ios app to fetch and display personalized offers using a simple post request and contextual payload in this section, youβll implement a utility function to retrieve and normalize offers for use in your ui for complete details on moments api, refer to the moments api documentation https //docs adspostx com/moments api#rqin step 1 fetch offers in this step, build a function that sends a post request to the moments api docid\ duaxqa4vbbpvihg4cqaac ( native/v4/offers json ) and returns personalized offers based on the given user context build and validate input parameters start by validating optional parameters like loyaltyboost and creative, then prepare the payload body // validate parameters if let loyaltyboost = loyaltyboost, !\["0", "1", "2"] contains(loyaltyboost) { throw offerserror invalidparameter(message "invalid loyaltyboost \\(loyaltyboost)") } if let creative = creative, !\["0", "1"] contains(creative) { throw offerserror invalidparameter(message "invalid creative \\(creative)") } // initialize payload var finalpayload = payload ?? \[ ] if isdevelopment { finalpayload\["dev"] = "1" } construct the request url use urlcomponents to construct the url and append query parameters like api key, loyaltyboost, creative, and campaignid var urlcomponents = urlcomponents(string "https //api adspostx com/native/v4/offers json") var queryitems = \[urlqueryitem(name "api key", value apikey)] if let loyaltyboost = loyaltyboost { queryitems append(urlqueryitem(name "loyaltyboost", value loyaltyboost)) } if let creative = creative { queryitems append(urlqueryitem(name "creative", value creative)) } if let campaignid = campaignid { queryitems append(urlqueryitem(name "campaignid", value campaignid)) } urlcomponents? queryitems = queryitems guard let url = urlcomponents? url else { throw offerserror invalidurl } configure and send the post request prepare the request with headers and payload body, then send the request using urlsession var request = urlrequest(url url) request httpmethod = "post" request setvalue("application/json", forhttpheaderfield "content type") request setvalue("application/json", forhttpheaderfield "accept") // set user agent from payload or fallback let useragent = finalpayload\["ua"] ?? getuseragent() request setvalue(useragent, forhttpheaderfield "user agent") // encode payload request httpbody = try jsonserialization data(withjsonobject finalpayload) parse the response and handle errors decode the api response, validate the result, and handle any decoding or network errors do { let (data, ) = try await urlsession shared data(for request) let response = try jsondecoder() decode(offersresponse self, from data) // ensure response contains offers guard let offers = response data? offers, !offers isempty else { throw offerserror nooffers } return response } catch let error as decodingerror { print("decoding error \\(error)") throw offerserror decodingerror } catch { throw offerserror networkerror(error) } full implementation fetching offers in ios func fetchoffers( apikey string, loyaltyboost string? = nil, creative string? = nil, isdevelopment bool = false, payload \[string string]? = nil, campaignid string? = nil ) async throws > offersresponse { // validate loyaltyboost parameter if provided if let loyaltyboost = loyaltyboost { guard \["0", "1", "2"] contains(loyaltyboost) else { throw offerserror invalidparameter(message "invalid loyaltyboost parameter \\(loyaltyboost)") } } // validate creative parameter if provided if let creative = creative { guard \["0", "1"] contains(creative) else { throw offerserror invalidparameter(message "invalid creative parameter \\(creative)") } } // construct the payload, ensuring 'dev' = '1' is included if isdevelopment is true var finalpayload = payload ?? \[ ] if isdevelopment { finalpayload\["dev"] = "1" } // baseurl https //api adspostx com/native/v4 // construct url with query parameters var urlcomponents = urlcomponents(string "\\(baseurl)/offers json") var queryitems = \[ urlqueryitem(name "api key", value apikey) ] // add optional parameters to query items if they exist if let loyaltyboost = loyaltyboost { queryitems append(urlqueryitem(name "loyaltyboost", value loyaltyboost)) } if let creative = creative { queryitems append(urlqueryitem(name "creative", value creative)) } if let campaignid = campaignid { queryitems append(urlqueryitem(name "campaignid", value campaignid)) } urlcomponents? queryitems = queryitems guard let url = urlcomponents? url else { throw offerserror invalidurl } // prepare request var request = urlrequest(url url) request httpmethod = "post" request setvalue("application/json", forhttpheaderfield "content type") request setvalue("application/json", forhttpheaderfield "accept") // set user agent header from payload\["ua"] or getuseragent() let useragent = finalpayload\["ua"] ?? getuseragent() request setvalue(useragent, forhttpheaderfield "user agent") // serialize the final payload request httpbody = try? jsonserialization data(withjsonobject finalpayload) do { let (data, ) = try await urlsession shared data(for request) let response = try jsondecoder() decode(offersresponse self, from data) // check if we have valid offers data guard let offers = response data? offers, !offers isempty else { throw offerserror nooffers } return response } catch let error as decodingerror { print("decoding error \\(error)") throw offerserror decodingerror } catch { throw offerserror networkerror(error) } } payload example let payload \[string string] = \[ "adpx fp" "\<unique value>", "pub user id" "\<unique value>", "placement" "checkout", "ua" "\<user agent value>" ] parameters the following table describes the parameters you can use when calling the fetchoffers function parameter type description required default apikey string the api key associated with your momentscience account yes β loyaltyboost string sets the loyalty boost level for the offers accepts "0", "1", or "2" no nil creative string determines the creative mode for the offers accepts "0" or "1" no nil isdevelopment bool enables development mode set to true for testing environments no false payload \[string string]? additional key value pairs to include in the request body no nil campaignid string? campaignid no nil common payload fields field type description adpx fp string device fingerprint or session id pub user id string a unique, non pii identifier for the end user placement string an attribute that represents the specific page, section, or location where the offer unit was triggered dev string use "1" to return test offers ua string user agent string response a successful response returns an object containing the available offers and related metadata for detailed response structure and field descriptions, refer to the moments api documentation see offerservice swift and offer swift in the demo app for a working implementation step 2 build the offer ui once offers are fetched, you need to build a ui to present them to users and allow interaction, such as accepting or dismissing the structure of this ui depends on your app architecture for a detailed explanation of how each field in the offer object is used refer to the offer anatomy docid\ t8o0a 3bctma448n5pyhw this guide will help you understand how to map api fields to ui components and apply dynamic styling correctly the examples below show how to implement this using swiftui they are intended to illustrate one possible approach and can be adapted freely to match your platform, design system, and navigation logic the offercontainerview , offersviewmodel and offerview shown below are example implementations you are free to structure your ui and state management according to your app's architecture these examples are provided to help you get started quickly offer container ui the offer container ui displays multiple offers in sequence and manages user navigation, loading, and error states below is a sample implementation using swiftui import swiftui /// a view that presents offers in a full screen modal interface struct offercontainerview view { /// view model that manages the offers and their state @observedobject var viewmodel offersviewmodel /// environment value for dismissing the view @environment(\\ dismiss) private var dismiss var body some view { zstack { // semi transparent background from api styling viewmodel getpopupbackgroundcolor() ignoressafearea() if viewmodel isloading { // loading state progressview("loading offers ") foregroundcolor( white) } else if let error = viewmodel error { // error state with retry option vstack(spacing 16) { text(error) foregroundcolor( red) padding() hstack(spacing 20) { button("close") { dismiss() } foregroundcolor( gray) button("try again") { viewmodel loadoffers() } foregroundcolor( blue) } } background(color white) clipshape(roundedrectangle(cornerradius 12)) padding() } else if let offer = viewmodel currentoffer { // main offer display container vstack(spacing 0) { // close button with beacon tracking hstack { spacer() button { task { await viewmodel fireclosebeacon() dismiss() } } label { image(systemname "xmark") imagescale( large) foregroundcolor( gray) padding() } } // main offer content offerview( offer offer, buttonstyles viewmodel styles? offertext, onpositivecta { // handle positive response if let clickurl = offer clickurl, let url = url(string clickurl), uiapplication shared canopenurl(url) { uiapplication shared open(url) } // navigate or dismiss if viewmodel hasnextoffer { viewmodel shownextoffer() } else { task { await viewmodel fireclosebeacon() dismiss() } } }, onnegativecta { // handle negative response if let beacons = offer beacons, let nothanksclickurl = beacons nothanksclick, !nothanksclickurl isempty, let url = url(string nothanksclickurl) { viewmodel firebeaconrequest(url url) } // navigate or dismiss if viewmodel hasnextoffer { viewmodel shownextoffer() } else { // on last offer, fire close beacon and dismiss task { // fire close beacon and wait for it to complete await viewmodel fireclosebeacon() // if no more offers, close the container dismiss() } } }, viewmodel viewmodel ) // navigation controls navigationbuttonsview( hasprevious viewmodel haspreviousoffer, hasnext viewmodel hasnextoffer, onprevious viewmodel showpreviousoffer, onnext viewmodel shownextoffer ) } background(color white) clipshape(roundedrectangle(cornerradius 12)) padding( horizontal) padding( vertical, 40) frame(maxwidth infinity, maxheight infinity) } } statusbar(hidden true) } } for more details, see offercontainerview\ swift and offersviewmodel swift individual offer ui each offer is displayed using the offerview component, which presents offer details such as title, image, description, and call to action buttons with dynamic styling the business logic and state for the offer presentation are managed by the offersviewmodel class, following the mvvm pattern offerview usage // display the current offer offerview( offer currentoffer, buttonstyles styles? offertext, onpositivecta { / handle accept / }, onnegativecta { / handle decline / }, viewmodel viewmodel ) for advanced usage and dynamic styling, refer to offerview\ swift , offerviewmodel swift https //github com/adspostx/examples/blob/main/ios native/momentsapidemoapp ios/msapidemoapp/msapidemoapp/viewmodels/offersviewmodel swift if you prefer to use your own layout or styling you can parse the offer response manually use the offer anatomy docid\ t8o0a 3bctma448n5pyhw documentation to map fields like title, image , cta yes , etc apply any visual styling or logic defined in your own architecture this approach gives you full control over the user experience, while still integrating with the core moments api logic step 3 track user interactions to monitor engagement and ensure accurate analytics, send tracking requests when users interact with offers this includes impressions, dismissals, and cta clicks create a function to fire tracking beacons define a utility function to send tracking pixels via http get requests /// sends a beacon request to the specified url for tracking user interactions func firebeaconrequest(url url) async throws { var request = urlrequest(url url) request httpmethod = "get" request setvalue("application/json", forhttpheaderfield "accept") do { let ( , ) = try await urlsession shared data(for request) } catch { // handle error throw error } } track when the offer container is closed send the beacons close beacon when a user dismisses the offer container func fireclosebeacon() async { guard let offer = currentoffer, let beacons = offer beacons, let closeurl = beacons close, !closeurl isempty, let url = url(string closeurl) else { return } do { try await offersservice firebeaconrequest(url url) } catch { print("close beacon request failed \\(error localizeddescription)") } } track when an offer is displayed send impression pixels when the offer is shown ( pixel and adv pixel url ) firing pixel private func firepixelrequestforcurrentoffer() { guard let offer = currentoffer, let pixelurl = offer pixel, !pixelurl isempty, let url = url(string pixelurl) else { return } task { do { try await offersservice firebeaconrequest(url url) } catch { print("pixel request failed \\(error localizeddescription)") } } } firing adv pixel url private func fireadvpixelrequestforcurrentoffer() { guard let offer = currentoffer, let advpixelurl = offer advpixelurl, !advpixelurl isempty, let url = url(string advpixelurl) else { return } task { do { try await offersservice firebeaconrequest(url url) } catch { print("pixel request failed \\(error localizeddescription)") } } } track when the negative cta is clicked fire the beacons no thanks click beacon when a user taps on negative cta if let beacons = offer beacons, let nothanksclickurl = beacons nothanksclick, !nothanksclickurl isempty, let url = url(string nothanksclickurl) { viewmodel firebeaconrequest(url url) } see offersservice swift https //github com/adspostx/examples/blob/main/ios native/momentsapidemoapp ios/msapidemoapp/msapidemoapp/services/offersservice swift and offersviewmodel swift in the demo app for a working implementation next steps we recommend that you go through the moments api implementation checklist to verify your integration completing this checklist ensures that all best practices and requirements are met for a successful moments api integration π’ if you're running into any issues while going through the integration process, feel free to contact us at help\@momentscience com