Bonaventure OgetoBy Bonaventure Ogeto|

Handle Paystack Webhooks in Android with Kotlin

Android apps cannot receive Paystack webhooks directly. Build a webhook endpoint on your backend, verify the x-paystack-signature, and update your database. Use Firebase Cloud Messaging (FCM) to push payment events to the Android app. In the Android app, handle FCM messages in a FirebaseMessagingService subclass and update the UI.

Architecture

Paystack fires webhooks to your server. Your server then notifies the Android app:

Paystack → Your Backend Server (webhook received + verified)
                     ↓
               Update Database
                     ↓
            FCM Push to Android App
                     ↓
        FirebaseMessagingService handles message
                     ↓
           Update ViewModel / UI

Backend Webhook Handler with FCM

// Node.js backend with Firebase Admin SDK
var crypto = require('crypto');
var admin = require('firebase-admin');

app.post('/api/webhooks/paystack',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    var sig = req.headers['x-paystack-signature'];
    var hash = crypto
      .createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
      .update(req.body)
      .digest('hex');

    if (hash !== sig) return res.status(400).send('Invalid signature');
    res.sendStatus(200);

    var event = JSON.parse(req.body.toString());

    if (event.event === 'charge.success') {
      var email = event.data.customer.email;
      var reference = event.data.reference;
      var amount = event.data.amount / 100;

      // Update order in DB
      await db.orders.markAsPaid(reference);

      // Get user's FCM token
      var user = await db.users.findByEmail(email);
      if (user?.androidFcmToken) {
        await admin.messaging().send({
          token: user.androidFcmToken,
          notification: {
            title: 'Payment Received',
            body: 'NGN ' + amount + ' confirmed. Ref: ' + reference
          },
          data: {
            event: 'charge.success',
            reference: reference,
            amount: String(amount)
          }
        });
      }
    }
  }
);

Android: Firebase Messaging Service

Add Firebase to your Android project, then create a messaging service:

// PaystackMessagingService.kt
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class PaystackMessagingService : FirebaseMessagingService() {

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        // Send the new token to your backend
        sendTokenToBackend(token)
    }

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        val event = message.data["event"]
        val reference = message.data["reference"]
        val amount = message.data["amount"]

        when (event) {
            "charge.success" -> {
                // Send a broadcast that the payment Activity/ViewModel can receive
                val intent = android.content.Intent("com.yourapp.PAYMENT_SUCCESS")
                intent.putExtra("reference", reference)
                intent.putExtra("amount", amount)
                sendBroadcast(intent)

                // Also show a notification
                showNotification("Payment Confirmed", "NGN $amount received")
            }
        }
    }

    private fun showNotification(title: String, body: String) {
        val notificationManager = getSystemService(NOTIFICATION_SERVICE)
            as android.app.NotificationManager
        val channelId = "payment_events"

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val channel = android.app.NotificationChannel(
                channelId, "Payment Events",
                android.app.NotificationManager.IMPORTANCE_HIGH
            )
            notificationManager.createNotificationChannel(channel)
        }

        val notification = androidx.core.app.NotificationCompat.Builder(this, channelId)
            .setContentTitle(title)
            .setContentText(body)
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setAutoCancel(true)
            .build()

        notificationManager.notify(System.currentTimeMillis().toInt(), notification)
    }

    private fun sendTokenToBackend(token: String) {
        // Use Retrofit to POST the token to your backend
        // Store it so your backend can target this device
    }
}

Register in AndroidManifest.xml:

<service
    android:name=".PaystackMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

Learn More

Key Takeaways

  • Paystack webhooks are HTTP POST requests to your server. Android apps cannot receive them directly.
  • Build the webhook handler on your backend (Node.js, Django, Spring Boot, etc.).
  • Use Firebase Cloud Messaging (FCM) to push payment confirmation events to the Android app.
  • Implement a FirebaseMessagingService in Android to receive and handle FCM messages.
  • Send the FCM token from Android to your backend so it can target specific devices with payment events.
  • For simpler setups, poll a backend status endpoint after checkout instead of using push notifications.

Frequently Asked Questions

Do I need Firebase for Paystack webhooks in Android?
No. Firebase is one option for delivering payment events to Android. You can also poll a backend status endpoint after checkout. Polling is simpler but less efficient. FCM is better for background notifications when the user is not actively using the app.
How do I send the FCM token to my backend from Android?
Override onNewToken in your FirebaseMessagingService. Call FirebaseMessaging.getInstance().getToken() in your main Activity on startup. Store the token in SharedPreferences and send it to your backend API when the user logs in or on first launch.
What if the user does not allow notifications on Android 13+?
Request the POST_NOTIFICATIONS permission at runtime on Android 13+ using ActivityCompat.requestPermissions. If denied, fall back to polling the backend status endpoint after checkout. Always provide an in-app payment status screen the user can check manually.
Can I receive Paystack webhooks directly in Android?
No. Android apps do not have a public HTTPS domain that Paystack can reach. You must have a server-side endpoint. If you want a serverless solution, use Supabase Edge Functions or Firebase Cloud Functions as your webhook receiver.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon