Sending Virtual Gifts - Android
Virtual gifting allows viewers to support hosts or speakers during a livestream by sending them digital tokens or "gifts." These gifts can be visually animated and even linked to leaderboards or reward systems.
This guide explains how to implement virtual gifting using your backend server together with VideoSDK PubSub.
How It Works
-
User Action: A viewer taps on a gift button to send a virtual gift to the host.
-
Server Verification:
- A request is made to your business backend to process the gift.
- The server authenticates the user, checks their wallet balance, deducts the gift value, and returns a success response.
-
Event Broadcast: Upon success, the client publishes a PubSub message to inform everyone in the room (host + other viewers) that a gift has been sent. Clients can then visually show an animation or notification.
Backend Responsibility
Your backend plays a central role in verifying, validating, and processing gift transactions. It should:
- Authenticate the request (token/session)
- Verify if the user has sufficient balance
- Deduct the gift cost
- Store the transaction in a database (optional but recommended)
- Respond with success or failure
Frontend Implementation
- Sending a Gift (Client to Backend + PubSub)
The gift details travel in the payload of the PubSub message. publish() is a suspend function, so call it from a coroutine. It throws a VideoSDKException when the livestream is not joined yet, is reconnecting, or the message could not be sent. By then your backend has already charged the gift, so handle that case.
// Data class for request body
data class GiftRequest(val senderId: String, val hostId: String, val giftType: String)
// Data class for response body
data class GiftResponse(val success: Boolean, val message: String?)
// Retrofit API interface
interface ApiService {
@POST("send-gift")
suspend fun sendGift(@Body request: GiftRequest): Response<GiftResponse>
}
// Gift sending logic with PubSub
fun sendGift(giftType: String, senderId: String, hostId: String, pubSub: Room.PubSub) {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = api.sendGift(GiftRequest(senderId, hostId, giftType))
if (response.isSuccessful && response.body()?.success == true) {
val payload = JSONObject()
.put("giftType", giftType)
.put("from", senderId)
.put("to", hostId)
pubSub.publish("GIFT", giftType, payload = payload)
} else {
Log.e("Gift", "Failed: ${response.body()?.message}")
}
} catch (e: VideoSDKException) {
// The gift is charged, but the message was not sent
Log.e("Gift", "Gift not announced: ${e.code} ${e.name}")
} catch (e: Exception) {
Log.e("Gift", "Error: ${e.message}")
}
}
}
- Displaying the Gift (All Clients)
Subscribe to the GIFT topic once the room is joined, for example from onRoomJoined(). subscribe() is a suspend function, so call it from a coroutine. PubSub messages arrive on the main thread.
// In your Activity
data class Gift(val from: String, val to: String, val type: String)
val gifts = mutableListOf<Gift>()
val giftListener = object : PubSubMessageListener {
override fun onMessageReceived(message: PubSubMessage) {
val payload = message.payload ?: return
val from = payload.optString("from", "Unknown")
val to = payload.optString("to", "Unknown")
val type = payload.optString("giftType", "gift")
val gift = Gift(from = from, to = to, type = type)
gifts.add(gift)
Toast.makeText(
applicationContext,
"🎁 $from sent a $type to $to",
Toast.LENGTH_SHORT
).show()
}
}
lifecycleScope.launch {
try {
room!!.pubSub.subscribe("GIFT", giftListener)
} catch (e: VideoSDKException) {
Log.e("Gift", "Subscribe failed: ${e.code} ${e.name}")
}
}
API Reference
The API references for all the methods and events utilized in this guide are provided below.
Got a Question? Ask us on discord

