Scratch cards in the mobile SDK
On mobile there are two ways to issue a scratch card, and they are not equivalent. This page covers both, explains why one is materially safer, and shows how to receive the reveal callback when the user scratches inside the SDK.
| Path | Who calls Cheggout | Recommended |
|---|---|---|
| Server-to-server | Your backend, with a signed envelope, after validating the order | Yes |
| Client SDK call | The app, through an SDK helper | Only where the server path is not viable |
Both paths end the same way: Cheggout returns a deep link, and the app launches the SDK with it to show the card.
Which path to use
A client-initiated issuance path allows requests that are not tied to a verified transaction — the app is asserting that a payment happened, and the app is the least trustworthy place to assert that from. The server path lets your backend validate the order first and only then make a signed request for a card.
The rule that follows from this: bind virtualId and extTransactionId to a verified order on
your server. Never accept them from client input. See
Security best practices.
Use the client SDK call when your architecture genuinely cannot route the event through a backend — for example a transaction that completes entirely inside a third-party payment component your server never sees. Even then, treat it as a temporary state and plan the server path.
Server-to-server issuance (recommended)
GetScratchCardForBank issues a card from your backend using the
signed envelope — the same mechanism as the session API.
| Environment | URL |
|---|---|
| Production | https://bankapi.cheggout.com/api/GetScratchCardForBank |
| UAT | https://bankapi-uat.cheggout.com/api/GetScratchCardForBank |
Flow
Request
The envelope is standard. The BDATA payload is:
{
"virtualId": "pub_8f14e45fceea167a",
"transDateAndTime": "2026-07-31 14:15:30",
"amount": "499.00",
"mcc": "5814",
"extTransactionId": "PUB_TXN_20260731_000117",
"campaignId": "902",
"deviceToken": "REPLACE_WITH_DEVICE_TOKEN"
}
| Field | Description |
|---|---|
virtualId | Your pseudonymous user identifier |
transDateAndTime | Transaction timestamp, documented in the form 2026-07-31 14:15:30 |
amount | Transaction amount. TBD The unit is not documented — confirm with Cheggout whether this field is rupees or paisa before going live, because sending the wrong unit changes which campaign tier a transaction qualifies for and will not surface as an error |
mcc | Merchant category code for the transaction |
extTransactionId | Your unique reference for this issuance. TBD Replay semantics for this endpoint are not documented — see GetScratchCardForBank |
campaignId | The campaign this issuance belongs to. This endpoint takes a campaign identifier, not a trigger code — see Trigger events |
deviceToken | Push device token, used for reward notifications |
Note that transDateAndTime here uses a different format from the dateandtime
(DDMMYYYYHHMMSS) used by the client SDK call and the headless GetReward. Do not share a
formatter between them.
curl -X POST https://bankapi-uat.cheggout.com/api/GetScratchCardForBank \
-H "Content-Type: application/json" \
-d '{
"CHANNELID": "YOURCHANNEL",
"SIGN": "REPLACE_WITH_SIGNATURE",
"BDATA": "<base64 of the payload above>"
}'
Response
Success, after verifying and decoding BDATA:
{
"status": true,
"deepLink": "https://click.cheggout.com?type=reward&scratchCardId=1",
"message": "success",
"banner": "https://cdn.example.com/scratch-card-banner.png"
}
Failure:
{
"status": false,
"deepLink": "",
"message": "failure",
"banner": ""
}
The failure case may come back unsigned and bare rather than inside a signed envelope. Check for the failure shape before attempting signature verification, or your client will throw on a legitimate "not eligible" answer.
status: false here carries the same meaning as everywhere else in this module: the user is not
eligible right now. Render nothing and move on.
Full contract: GetScratchCardForBank.
Client SDK issuance
The SDK exposes a helper that performs issuance directly from the app. It is the shortest path to a working demo and the weakest path for production.
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
triggerEventType | String | Yes | Predefined campaign or trigger code, for example UPI, WITHINBANK, IMPS, RTGS |
amount | Double | Yes | Two decimal places |
extTransactionId | String | Yes | Unique alphanumeric, maximum 256 characters |
dateandtime | String | Yes | Format DDMMYYYYHHMMSS |
virtualId | String | Yes | Your pseudonymous user identifier |
sessionId | String | Yes | Pass the literal " " — a single space — as documented |
subTriggerEventType | String | No | Finer-grained sub-category |
merchantVPA | String | Conditional | Required only for UPI |
additionalParams | String | No | A JSON string |
additionalParams accepts these optional inner keys: country, state, district, city,
pincode, storeId, ipaddress, lat, long, gender, age, modeType, isNewUser.
Unlike the marketplace launch, this call takes the literal " " — a single space — for
sessionId. It is an issuance call, not a session handoff. The source material calls this "an empty
string" and then writes it as " "; pass the single space exactly as documented.
TBD Whether a truly empty string "" is also accepted is not documented.
Confirm with Cheggout before changing it. See
Session and SSO for where a real sessionId belongs.
Code
- Android — Kotlin
- iOS — Swift
CHEGNetworkCallHelper.Companion.getScratchCardDetails(
triggerEventType = "UPI",
amount = 499.00,
extTransactionId = "PUB_TXN_20260731_000117",
dateandtime = "31072026141530",
virtualId = "pub_8f14e45fceea167a",
sessionId = " ",
subTriggerEventType = "",
merchantVPA = "merchant@examplebank",
object : CHEGScratchCardCallBack {
override fun onResponse(responseString: String?) {
// responseString carries status, banner, deepLink and popup fields.
// Parse it, show the banner, and launch the SDK with the deep link on tap.
}
}
)
CheggoutApp.sendRewardCardRequest(
triggerEventType: "UPI",
subTriggerEventType: "",
merchantVPA: "merchant@examplebank",
amount: 499.00,
extTransactionId: "PUB_TXN_20260731_000117",
dateandtime: "31072026141530",
virtualId: "pub_8f14e45fceea167a",
sessionId: " ",
additionalParams: ""
) { scratchCard in
// scratchCard carries status, banner, deepLink and popup fields.
// Show the banner, then launch the SDK with the deep link on tap.
}
Response
{
"message": "Success",
"status": true,
"banner": "https://cdn.example.com/scratch-card-banner.png",
"deepLink": "https://click.cheggout.com?type=reward&scratchCardId=1",
"isPopup": true,
"popupBanner": "https://cdn.example.com/scratch-card-popup.png",
"popupDeepLink": "https://click.cheggout.com?type=reward&scratchCardId=5"
}
| Field | Meaning |
|---|---|
status | Whether a card was issued |
banner | Creative to display inline in your app |
deepLink | Launch this in the SDK to open the card |
isPopup | Whether Cheggout also wants a modal presentation |
popupBanner | Creative for the modal |
popupDeepLink | Deep link for the modal's card |
When isPopup is true you have been given two presentations to render — an inline banner and a
modal — each with its own deep link. Respect the flag rather than always showing one or the other.
Launching the card
Both issuance paths hand you a deepLink. Pass it to the SDK launch method in the payload's
DeeplinkInfo slot.
- Android — Kotlin
- iOS — Swift
val payload = "{'Component':'CHEGGOUT'," +
"'IsEmployee':'0'," +
"'Language':'EN'," +
"'DeeplinkInfo':'$deepLink'," +
"'customerId':''}"
CheggoutApplication.launchCheggout(
this,
virtualId,
sessionId,
payload,
activityContext,
channelName
)
import CheggoutBridge
let payload: [String: Any] = [
"Component": "CHEGGOUT",
"IsEmployee": "0",
"Language": "EN",
"DeeplinkInfo": deepLink
]
cheggoutApp = CheggoutApp(
userID: virtualId,
sessionInfo: sessionId,
accessInfo: nil,
payload: payload,
exitHandler: { _ in
// Clear the Cheggout instance and return to your own UI.
}
)
An invalid userID or sessionInfo prevents the SDK from proceeding past the claimer page,
resulting in a blank page rather than a message. If you see a white SDK screen, check the identity
values first.
Reveal notifications
When the user scratches a card inside the SDK, the SDK tells your app what was revealed. Use this to update your own store, refresh a rewards badge, or trigger your own analytics.
- Android — Kotlin
- iOS — Swift
class ShopActivity : AppCompatActivity(), CHEGscratchRevealCallBack {
override fun onReveal(onRevealData: String?) {
// onRevealData carries the revealed scratch card details.
// Persist it, then refresh any rewards UI you own.
Log.d("Cheggout", "onReveal: $onRevealData")
}
}
Implement CHEGscratchRevealCallBack in the Activity that launches the SDK and override
onReveal().
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification(_:)),
name: Notification.Name("HandleDominosScDetails"),
object: nil
)
@objc func handleNotification(_ notification: Notification) {
// The SDK broadcasts the scratch card details as scDetails.
// Persist them, then refresh any rewards UI you own.
let scDetails = notification.userInfo?["scDetails"]
print("Cheggout reveal: \(String(describing: scDetails))")
}
The SDK posts a notification named HandleDominosScDetails carrying scDetails. Observe it via
NotificationCenter in the screen that presents the SDK.
TBD The reveal callback payload schema is not documented. Both callbacks are
described as delivering "the scratch card details" without a field list, and the iOS notification
name is product-specific in a way that suggests it may change. What is missing: the exact JSON
schema of onRevealData and scDetails, and whether a miss
(BETTER LUCK NEXT TIME) is delivered through the same callback. Parse defensively and confirm
during UAT. Tracked in Open Items.
Tracking
Inside the SDK, impression and click tracking for the card itself is handled by the SDK. If you
render the returned banner in your own UI, you are showing a Cheggout creative in a publisher
surface and the advertisement tracking contract applies to it.
Checklist
- Issuance happens server-side, after transaction validation, wherever possible.
-
virtualIdandextTransactionIdare derived server-side, never taken from the client. -
status: falserenders nothing and is not treated as an error. - Unsigned failure bodies are handled on the server path.
-
isPopupis respected rather than ignored. - The reveal callback is implemented and persists what it receives.
- Retries reuse the original
extTransactionId.
Next
- Lifecycle — states, idempotency and the one-shot reveal.
- Trigger events — the codes shown above and how to get yours.
- Session and SSO — how the SDK gets its session.