API Documentation
Native App Integration Guide
Complete API reference for integrating payment tiers, access control, and location data into iOS and Android apps
Base URL
https://walkingintheirfootsteps.comPayment Integration Workflow
Step-by-Step Implementation:
- Call
GET /getPassesto display available tiers - User selects a pass → Call
POST /createCheckoutwith deep link URLs - Open
checkout_urlin webview or browser - Stripe processes payment and redirects to
success_url - Call
GET /getUserPassesto refresh user's active passes - Cache passes locally for offline access checks
GET
/getPassesFetch all active pass tiers for purchase
Request:
GET https://walkingintheirfootsteps.com/getPassesResponse:
{
"success": true,
"data": [
{
"id": "pass_1",
"name": "All Access Pass",
"description": "Unlimited access to all locations",
"type": "all_access",
"price_eur": 29.99,
"duration_days": 365,
"features": ["All locations", "Offline downloads"],
"is_featured": true
},
{
"id": "pass_2",
"name": "D-Day Campaign Pass",
"type": "campaign",
"price_eur": 9.99,
"duration_days": 180,
"included_campaigns": ["D-Day"]
}
]
}GET
/getUserPassesAuth Required
Get authenticated user's purchased passes
Request:
GET https://walkingintheirfootsteps.com/getUserPasses
Authorization: Bearer <USER_JWT_TOKEN>Response:
{
"success": true,
"has_active_pass": true,
"data": [
{
"purchase_id": "pur_123",
"pass_id": "pass_1",
"pass_name": "All Access Pass",
"pass_type": "all_access",
"included_campaigns": [],
"included_location_ids": [],
"purchased_at": "2026-01-15T10:00:00Z",
"expires_at": "2027-01-15T10:00:00Z",
"is_lifetime": false,
"is_expired": false
}
]
}POST
/createCheckoutAuth Required
Create Stripe checkout session for pass purchase
Request:
POST https://walkingintheirfootsteps.com/createCheckout
Content-Type: application/json
Authorization: Bearer <USER_JWT_TOKEN>
{
"pass_id": "pass_1",
"success_url": "yourapp://payment/success",
"cancel_url": "yourapp://payment/cancel"
}Response:
{
"success": true,
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_...",
"session_id": "cs_test_..."
}Complete Integration Example (Swift/iOS)
// 1. Fetch available passes
let passes = await fetchPasses()
displayPassesInUI(passes)
// 2. User selects pass, create checkout
let checkoutData = await createCheckout(
passId: selectedPass.id,
successUrl: "yourapp://payment/success",
cancelUrl: "yourapp://payment/cancel"
)
// 3. Open Stripe checkout
presentWebView(url: checkoutData.checkout_url)
// 4. Handle success callback (from deep link)
func handlePaymentSuccess() {
let userPasses = await getUserPasses()
cacheUserPasses(userPasses)
showSuccessMessage()
}
// 5. Before showing location content
func canAccessLocation(locationId: String) -> Bool {
let access = await checkLocationAccess(locationId: locationId)
return access.has_access
}
// OR check locally from cached location data
let location = cachedLocations.first { $0.id == locationId }
if location.has_access {
// Show content
} else if location.access_reason == nil {
// Show paywall
}
// Access reasons:
// - "free" → Always show
// - "sponsored" → Always show (with sponsor credit)
// - "all_access_pass" → User has All Access
// - "campaign_pass" → User has campaign pass
// - "single_location_pass" → User has single location pass
// - nil/missing → Show paywallComplete Integration Example (Kotlin/Android)
// 1. Fetch and display passes
val passes = apiService.getPasses()
recyclerView.adapter = PassesAdapter(passes)
// 2. Create checkout session
val checkout = apiService.createCheckout(
passId = selectedPass.id,
successUrl = "yourapp://payment/success",
cancelUrl = "yourapp://payment/cancel"
)
// 3. Open checkout in Chrome Custom Tab
val intent = CustomTabsIntent.Builder()
.build()
.launchUrl(context, Uri.parse(checkout.checkoutUrl))
// 4. Handle success in deep link activity
override fun onNewIntent(intent: Intent) {
if (intent.data?.toString()?.contains("payment/success") == true) {
lifecycleScope.launch {
val userPasses = apiService.getUserPasses()
cacheUserPasses(userPasses)
showSuccessDialog()
}
}
}
// 5. Access control before content display
suspend fun canAccessLocation(locationId: String): Boolean {
val access = apiService.checkLocationAccess(locationId)
return access.hasAccess
}
// OR use cached location data
val location = locationCache[locationId]
when (location?.accessReason) {
"free", "sponsored" -> showContent()
"all_access_pass", "campaign_pass", "single_location_pass" -> showContent()
else -> showPaywall()
}Important Notes
✅ All endpoints return JSON
✅ Location data includes has_access boolean
✅ Free and sponsored locations automatically have has_access = true
✅ Cache user passes locally for offline access checks
✅ Use deep links for Stripe redirect URLs
⚠️ Webhook handles purchase completion automatically
Access Reason Values
free
Location is not premiumsponsored
Active sponsorshipall_access_pass
User has All Access Passcampaign_pass
User has Campaign Passsingle_location_pass
User has Single Location Passnull/missing
No access - show paywall