Android apps cannot call the Paystack API with a secret key. Initialize the transaction on your backend and return the authorization_url. Open it in a WebView Activity in Android. Add a WebViewClient that intercepts the callback URL redirect, extracts the reference, and sends it to your backend for verification via Retrofit or OkHttp.
Add INTERNET permission to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Add dependencies to build.gradle.kts (app module):
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
// WebView is included in Android SDK
}
// data/PaymentApiService.kt
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
data class InitializeRequest(val email: String, val amount: Double)
data class InitializeResponse(
val authorization_url: String,
val access_code: String,
val reference: String
)
data class VerifyResponse(
val verified: Boolean,
val amount: Double?,
val currency: String?,
val reference: String?
)
interface PaymentApiService {
@POST("api/pay/initialize")
suspend fun initialize(@Body request: InitializeRequest): InitializeResponse
@GET("api/pay/verify")
suspend fun verify(@Query("reference") reference: String): VerifyResponse
}
// RetrofitClient.kt
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
private val retrofit = Retrofit.Builder()
.baseUrl("https://your-backend.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val paymentApi: PaymentApiService = retrofit.create(PaymentApiService::class.java)
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
// PaymentActivity.kt
import android.annotation.SuppressLint
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class PaymentActivity : AppCompatActivity() {
private lateinit var webView: WebView
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
webView = WebView(this)
setContentView(webView)
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
val authUrl = intent.getStringExtra("authorization_url") ?: run {
finish()
return
}
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
val url = request?.url?.toString() ?: return false
// Detect callback URL
if (url.startsWith("https://yourapp.com/payment/callback")) {
val uri = android.net.Uri.parse(url)
val reference = uri.getQueryParameter("reference")
?: uri.getQueryParameter("trxref")
if (reference != null) {
verifyPayment(reference)
}
return true
}
return false
}
}
webView.loadUrl(authUrl)
}
private fun verifyPayment(reference: String) {
lifecycleScope.launch {
try {
val result = RetrofitClient.paymentApi.verify(reference)
val intent = android.content.Intent()
intent.putExtra("reference", reference)
intent.putExtra("verified", result.verified)
intent.putExtra("amount", result.amount ?: 0.0)
intent.putExtra("currency", result.currency)
setResult(
if (result.verified) RESULT_OK else RESULT_CANCELED,
intent
)
} catch (e: Exception) {
setResult(RESULT_CANCELED)
}
finish()
}
}
}
Register the Activity in AndroidManifest.xml:
<activity android:name=".PaymentActivity" android:exported="false" />
// MainActivity.kt or CheckoutFragment.kt
class CheckoutActivity : AppCompatActivity() {
private val paymentLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
val reference = result.data?.getStringExtra("reference")
val amount = result.data?.getDoubleExtra("amount", 0.0)
val currency = result.data?.getStringExtra("currency")
showPaymentSuccess(reference, amount, currency)
} else {
showPaymentFailed()
}
}
private fun startPayment(email: String, amountNGN: Double) {
lifecycleScope.launch {
try {
val response = RetrofitClient.paymentApi.initialize(
InitializeRequest(email, amountNGN)
)
val intent = Intent(this@CheckoutActivity, PaymentActivity::class.java)
intent.putExtra("authorization_url", response.authorization_url)
paymentLauncher.launch(intent)
} catch (e: Exception) {
showError("Failed to initialize payment")
}
}
}
}
This guide is part of the Paystack integration guides by language and framework series.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course