MatchPass SDK
Add M-Pesa pay-per-view passes to any Android app in under an hour. One composable. Zero payment infrastructure. Users pay with their phone — your content unlocks instantly.
How it works
MatchPass sits between your content and your users. When a user taps locked content, show the Paywall composable. It handles everything — phone verification, M-Pesa STK push, pass issuing, and expiry. Your code only needs to respond to onAccessGranted.
User taps locked content
│
▼
MatchPassSDK.Paywall()
│
├─ Existing pass? ──────────────────────► onAccessGranted (silent)
│
├─ Phone known? ─── Yes ───────────────► Confirm screen
│ │ │
│ No ▼
│ │ M-Pesa STK Push sent
│ ▼ │
│ Phone entry User enters PIN on phone
│ │ │
│ OTP verification Pass issued automatically
│ │
└─────────────────────────────────────────────▼
onAccessGranted(grant)Installation
1. Add JitPack to settings.gradle.kts
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}2. Add the dependency
// app/build.gradle.kts
dependencies {
implementation("com.github.fatahrez:MatchPassAndroidSDK:1.0.0-beta01")
}Quick Start
1. Initialise once in Application.onCreate()
Get your API key from staging.matchpass.africa. Register as an operator — your key is generated instantly and shown once.
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
MatchPassSDK.init(
context = this,
config = MatchPassConfig(
apiKey = "YOUR_API_KEY", // from staging.matchpass.africa
baseUrl = "https://staging.api.matchpass.africa/api/v1/",
debug = BuildConfig.DEBUG,
),
)
}
}2. Show the paywall when a user taps locked content
// In any composable — show when a user taps locked content
var showPaywall by remember { mutableStateOf(false) }
if (showPaywall) {
MatchPassSDK.Paywall(
content = MatchPassContent(
id = "arsenalmancity01jul26",
title = "Arsenal vs Manchester City",
price = "299",
currency = "KSh",
durationHours = 4,
contentType = ContentType.MATCH,
),
userPhone = sessionPhone, // null → SDK asks; set → skips OTP
onAccessGranted = { grant ->
showPaywall = false
startStream(grant.token) // pass token to your streaming backend
},
onDismiss = { showPaywall = false },
)
}3. Check access without UI (for lock icons)
// Use this to show a lock icon or play button on content cards — no UI shown
LaunchedEffect(content.id) {
when (val result = MatchPassSDK.checkAccess(context, content)) {
is AccessResult.Granted -> showPlayButton(result.grant.token)
is AccessResult.Expired -> showRenewButton()
is AccessResult.NotPurchased -> showLockIcon()
is AccessResult.Error -> showLockIcon() // fail closed
}
}Content Types
Pass the right ContentType and the SDK picks the correct validation frequency, cache TTL, and pass label automatically.
| ContentType | Use case | Cache TTL | Pass label |
|---|---|---|---|
MATCH | Live sport, single event | 5 min | Game Pass |
CHANNEL | Live channel, time-limited | 60 s | Channel Pass |
SEASON | Full TV season, rewatch | 24 h | Season Pass |
MOVIE | Movie, own permanently | 30 days | Own it |
Content IDs
Content IDs are how MatchPass links a pass to a piece of content. You own the ID — pick something human-readable and date-stamped for live events so the same fixture in a future season gets a new ID and a fresh pass.
// ✅ Recommended — unique per fixture and date id = "arsenalmancity01jul26" id = "chiefspirates03jul26" id = "boxing-riyadhseason05jul26" // ✅ Stable IDs for channels and series (content repeats) id = "ch-supersport1" id = "series-shaka-ilembe-s1" // ❌ Avoid — generic IDs cause pass collisions across seasons id = "match-001"
API Reference
MatchPassSDK
| Method | Returns | Description |
|---|---|---|
Builder(context) | Builder | Fluent SDK initialiser. Call once from Application.onCreate(). |
Paywall(...) | Unit (Composable) | Show the full purchase flow. Handles phone, OTP, M-Pesa, and pass issuing. |
Login(...) | Unit (Composable) | Standalone phone OTP screen. Call before browsing if your app requires login. |
checkAccess(...) | AccessResult (suspend) | Validate access silently. Use for lock icons and stream gates. |
setPhone(...) | Unit | Pre-set a verified phone from your own auth — skips OTP in the paywall. |
getStoredPhone(...) | String? | Read the verified phone stored on this device. |
signOut(...) | Unit | Clear the session. Does NOT revoke passes. |
getExpiresAt(...) | Long? | Epoch-ms expiry for a content pass. Use for countdown timers. |
MatchPassContent
MatchPassContent(
id = "arsenalmancity01jul26", // your stable content identifier
title = "Arsenal vs Man City",
price = "299", // string, in the currency below
currency = "KSh",
durationHours = 4, // how long the pass is valid after purchase
contentType = ContentType.MATCH, // MATCH | CHANNEL | SEASON | MOVIE
)AccessResult
| Subtype | Fields | Meaning |
|---|---|---|
Granted | grant: MatchPassGrant | Valid pass — present grant.token to your streaming backend |
Expired | expiredAt: String | Pass found but expired — show renewal CTA |
NotPurchased | — | No pass exists — show paywall |
Error | exception: ... | Network or server error — fail closed (show lock) |
Session management
// Read the stored phone (null if not logged in) val phone: String? = MatchPassSDK.getStoredPhone(context) // Clear session — does NOT revoke passes MatchPassSDK.signOut(context) // Get expiry epoch-ms for a specific piece of content (for countdown timers) val expiresAt: Long? = MatchPassSDK.getExpiresAt(context, contentId)
Pre-set phone from your own auth
If your app has its own login system, pass the verified phone to MatchPass so the paywall skips its OTP step.
// After your own login succeeds, hand the verified phone to MatchPass MatchPassSDK.setPhone(context, verifiedPhone) // The next Paywall() call will skip OTP and go straight to M-Pesa
Pass Restore
When a user reinstalls or switches devices, the SDK automatically checks the server for an existing active pass before showing the paywall. If found, onAccessGrantedfires silently — no re-purchase required. This works automatically when the user's phone is known.
Debug & Local Dev
MatchPassSDK.init(
context = this,
config = MatchPassConfig(
apiKey = "your-staging-key",
baseUrl = "http://10.0.2.2:8000/api/v1/", // emulator → host machine localhost
debug = true, // OTP shown on-screen, verbose logs
),
)In debug mode the OTP code is shown on-screen — no real SMS is sent. Add cleartext traffic config for emulator access:
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">10.0.2.2</domain>
</domain-config>
</network-security-config>Ready to integrate?
Register as an operator to get your API key and start issuing passes.
Create operator account →