iOS callbacks and notifications
The iOS SDK communicates back through three distinct mechanisms, and knowing which one carries what saves a lot of searching:
| Mechanism | Used for | Where you handle it |
|---|---|---|
| Completion closures | Results of data method calls | At the call site |
exitHandler | SDK exit, and deep payment data | The closure you passed at launch |
NotificationCenter broadcasts | User activity heartbeat, reward reveal | An observer you register |
Android uses listener interfaces for all three. iOS splits them, which means an integration ported from Android will silently miss the notifications unless you register observers explicitly.
Completion closures
Every data method on CheggoutApp takes a trailing closure that receives the
result. They share three properties worth internalising:
- No error parameter. There is no
Resulttype and noError. Failure surfaces as an empty or nil payload, or as a body whosestatusisfalse. - No queue guarantee. TBD The documentation does not state which queue the closure is called on. Assume a background queue and dispatch to the main queue before touching UI.
- No retry. A dropped request is simply a closure that never fires or fires empty. If the data matters, add your own timeout.
CheggoutApp.getCheggoutTopBanners(
languageCode: "EN",
virtualId: virtualId,
placementId: "01",
additonalParams: "",
securityKey: ""
) { topBanners in
DispatchQueue.main.async {
guard let banners = topBanners, !banners.isEmpty else {
self.hideBannerCarousel() // a normal outcome, not an error state
return
}
self.render(banners)
}
}
| Method | Closure delivers |
|---|---|
getCheggoutTopBanners | Banner creatives keyed by placement id |
getCheggoutTopStores | An array of stores with logo, title and discount |
getCashbackPoints | cashbackPoints (a string) and deepLink |
sendRewardCardRequest | Scratch card status, banner, deepLink and popup fields |
impression has no closure — it fires a beacon and returns.
When a banner closure fires you have data, not an impression. Call
CheggoutApp.impression(userID:impressionInfo:) when the creative is actually on screen. Firing on
fetch inflates impressions for creatives the user never scrolled to, and misattributes the ones they
did see.
exitHandler
exitHandler is passed into the CheggoutApp initialiser and called when the SDK exits. It carries
two responsibilities and, in one flow, one payload.
cheggoutApp = CheggoutApp(
userID: virtualId,
sessionInfo: sessionId,
accessInfo: nil,
payload: payload,
exitHandler: { [weak self] result in
DispatchQueue.main.async {
self?.dismiss(animated: false)
self?.cheggoutApp = nil
}
}
)
Responsibility one — dismiss. The SDK does not tear down your presentation for you.
Responsibility two — release. Clear the initialised Cheggout instance. Holding it after exit keeps the SDK's state alive and makes the next launch ambiguous.
After control returns, where the user lands depends on your own session state: if your app ends the session the user goes to your login page; if you keep it alive using Last Activity Time or heartbeat, they go to your home page. See TrackUserActivity below for keeping that session alive.
Payment data arrives through exitHandler
In the deep payment flow, the SDK exits so that your app can collect the money. The payment data
response is passed as a parameter to the exitHandler closure, Base64 encoded.
exitHandler: { [weak self] result in
guard
let encoded = result,
let data = Data(base64Encoded: encoded),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
else {
DispatchQueue.main.async { self?.dismiss(animated: false) }
return
}
self?.handlePayment(json)
}
Decoded, it looks like this:
{
"deepLink": "https://click.cheggout.com?type=pwa&id=<giftcard url>?token=tokenvalue/cheggoutRefNo=YOURCHANNEL123456/paymentType=YOURCHANNEL/amount=540.00/transactionId=bankTransactionID/status=bankStatus",
"cheggoutRefNo": "YOURCHANNEL123456",
"amount": "54000",
"responseCode": "COLLECTPAYMENT",
"virtualId": "pub_8f14e45fceea167a",
"merchantName": "Cheggout",
"merchantId": "REPLACE_WITH_MERCHANT_ID"
}
| Field | Meaning |
|---|---|
deepLink | Used to relaunch the SDK after payment. Replace bankTransactionID with your payment's transaction ID and bankStatus with the payment status. |
cheggoutRefNo | Unique reference number generated by Cheggout for the transaction. Store it — it is your reconciliation key. |
amount | Payment amount in paisa. "54000" means ₹540.00. |
responseCode | COLLECTPAYMENT. |
virtualId | The user this payment belongs to. |
merchantName | Identifies the transaction source within Cheggout, for example Cheggout. |
merchantId | A unique identifier per merchant name, issued by the bank during onboarding. |
amount is in paisaEvery other amount in the Cheggout contract is in rupees to two decimals. This one is in paisa. Convert explicitly and assert on the result before charging anything.
Once the payment completes, relaunch the SDK using the deep link, with bankTransactionID and
bankStatus substituted. Full flow in
Deep payment.
exitHandler fires both on an ordinary back-out and on a payment handoff. Branch on whether the
parameter decodes to a payload with responseCode: "COLLECTPAYMENT"; an ordinary exit will not.
TrackUserActivity — the heartbeat
Android exposes a CHEGHeartBeatListener interface. iOS instead posts a notification, which
your app observes.
The SDK posts Notification.Name("TrackUserActivity") with a userInfo dictionary containing
LastUserActivity and an event key with the value "tap".
NotificationCenter.default.addObserver(
forName: Notification.Name("TrackUserActivity"),
object: nil,
queue: .main
) { notification in
// The user is active inside the Cheggout SDK.
// Reset your own inactivity timer so the session does not expire mid-shop.
SessionManager.shared.recordActivity()
if let info = notification.userInfo {
let last = info["LastUserActivity"]
let event = info["event"] // "tap"
_ = (last, event)
}
}
Register the observer before you launch the SDK, and remove it when your hosting view controller goes away.
Why this matters: without it, a user who spends fifteen minutes browsing inside Cheggout looks idle to your app's session timer. They come back to a login screen instead of your home screen, and they blame the shopping module for logging them out.
TBD The type and format of LastUserActivity, the full set of possible event
values beyond "tap", and the posting cadence are not documented. Treat each notification as
"activity happened just now" and reset your timer, rather than computing elapsed time from the
values or from the gap between posts.
HandleDominosScDetails — reward reveal
When a reward is revealed inside the SDK, the scratch card details are broadcast as a notification so the parent app can react — refresh a points balance, log an analytics event, or show your own confirmation.
The SDK sends the scratch card details (scDetails) via a notification broadcast named
HandleDominosScDetails. Listen for it and handle scDetails in your handler.
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification(_:)),
name: Notification.Name("HandleDominosScDetails"),
object: nil
)
@objc func handleNotification(_ notification: Notification) {
let scDetails = notification.userInfo?["scDetails"]
print("Reveal details: \(String(describing: scDetails))")
rewardsRepository.refresh()
}
HandleDominosScDetails is the actual broadcast name in the SDK. It reads like a leftover from a
specific campaign, but it is the name you must observe. Do not rename it, and do not assume it is
brand-specific — it carries reveal details for any reward.
TBD The exact structure of scDetails, and the key it is filed under in
userInfo, are not documented. Log the notification in UAT to see what your build delivers, and do
not parse fields you have not observed. The Android equivalent is the CHEGscratchRevealCallBack
interface; see Android callbacks.
Push notifications
Push is not a callback mechanism — Cheggout needs to reach the user when your app is not running — and the two integration options are identical across platforms. Rather than duplicating them, they are documented once:
In short: either Cheggout calls your push API with the user's VirtualID, a message and the
deep-link information, or you pass the Firebase identifier as pushnotificationID in the launch
payload and hand Cheggout your FCM credentials so it sends directly.
On tap, whichever option you chose, launch the SDK with the deep link in the payload's
DeeplinkInfo:
let payload: [String: Any] = [
"Component": "CHEGGOUT",
"IsEmployee": "0",
"Language": "EN",
"DeeplinkInfo": deepLinkFromNotification
]
Next
- Launching — the initialiser and the payload the deep link goes into.
- SDK methods — the calls these closures belong to.
- Deep payment — the full payment round trip.