iOS 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 WKWebView in Swift. Implement WKNavigationDelegate to detect when Paystack redirects to your callback_url, extract the reference, and verify on your backend using URLSession.
Call your backend from Swift to get the authorization URL:
// PaymentService.swift
struct InitializeResponse: Codable {
let authorization_url: String
let access_code: String
let reference: String
}
struct InitializeRequest: Codable {
let email: String
let amount: Double
}
class PaymentService {
static let shared = PaymentService()
private let baseURL = "https://your-backend.com"
func initialize(email: String, amount: Double) async throws -> InitializeResponse {
guard let url = URL(string: "(baseURL)/api/pay/initialize") else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(InitializeRequest(email: email, amount: amount))
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(InitializeResponse.self, from: data)
}
}
// PaymentViewController.swift
import UIKit
import WebKit
protocol PaymentViewControllerDelegate: AnyObject {
func paymentDidSucceed(reference: String)
func paymentDidFail()
func paymentWasCancelled()
}
class PaymentViewController: UIViewController, WKNavigationDelegate {
private var webView: WKWebView!
private let authorizationURL: String
private let callbackURLPrefix = "https://yourapp.com/payment/callback"
weak var delegate: PaymentViewControllerDelegate?
init(authorizationURL: String) {
self.authorizationURL = authorizationURL
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError() }
override func viewDidLoad() {
super.viewDidLoad()
title = "Complete Payment"
navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .cancel,
target: self, action: #selector(cancel)
)
webView = WKWebView(frame: view.bounds)
webView.navigationDelegate = self
view.addSubview(webView)
if let url = URL(string: authorizationURL) {
webView.load(URLRequest(url: url))
}
}
@objc func cancel() {
delegate?.paymentWasCancelled()
dismiss(animated: true)
}
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.url?.absoluteString ?? ""
if url.hasPrefix(callbackURLPrefix) {
// Extract reference from callback URL
if let components = URLComponents(string: url),
let reference = components.queryItems?.first(where: {
$0.name == "reference" || $0.name == "trxref"
})?.value {
decisionHandler(.cancel)
delegate?.paymentDidSucceed(reference: reference)
dismiss(animated: true)
return
}
}
decisionHandler(.allow)
}
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
// PaymentWebView.swift (SwiftUI)
import SwiftUI
import WebKit
struct PaystackWebView: UIViewRepresentable {
let authURL: String
let callbackPrefix: String
var onSuccess: (String) -> Void
var onCancel: () -> Void
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
if let url = URL(string: authURL) {
webView.load(URLRequest(url: url))
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, WKNavigationDelegate {
var parent: PaystackWebView
init(parent: PaystackWebView) { self.parent = parent }
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.url?.absoluteString ?? ""
if url.hasPrefix(parent.callbackPrefix),
let components = URLComponents(string: url),
let reference = components.queryItems?.first(where: {
$0.name == "reference" || $0.name == "trxref"
})?.value {
decisionHandler(.cancel)
parent.onSuccess(reference)
return
}
decisionHandler(.allow)
}
}
}
// Usage in a SwiftUI view:
// PaystackWebView(
// authURL: authURL,
// callbackPrefix: "https://yourapp.com/payment/callback",
// onSuccess: { reference in verifyPayment(reference) },
// onCancel: { dismiss() }
// )
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