Walking in Their Footsteps
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.com
Payment Integration Workflow

Step-by-Step Implementation:

  1. Call GET /getPasses to display available tiers
  2. User selects a pass → Call POST /createCheckout with deep link URLs
  3. Open checkout_url in webview or browser
  4. Stripe processes payment and redirects to success_url
  5. Call GET /getUserPasses to refresh user's active passes
  6. Cache passes locally for offline access checks
GET
/getPasses

Fetch all active pass tiers for purchase

Request:

GET https://walkingintheirfootsteps.com/getPasses

Response:

{
  "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
/getUserPasses
Auth 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
/createCheckout
Auth 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 paywall
Complete 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 premium

sponsored
Active sponsorship

all_access_pass
User has All Access Pass

campaign_pass
User has Campaign Pass

single_location_pass
User has Single Location Pass

null/missing
No access - show paywall