Transaction Metadata on Android
Attach your own JSON context to a sale, preauthorization, or refund with the optional metadata parameter. These examples require Android SDK 1.0.7 or later and work in UAT and Production. For a supplied AAR, follow Manual AAR Installation.
What is stored
Metadata is an optional JSON object containing your application's context, such as an order ID, invoice reference, cart ID, or refund reason. Use it to reconcile Koard events with your own records and explain why a payment operation occurred.
It belongs to the individual transaction event that the request creates. It is separate from eventId (idempotency), transactionId (the payment lifecycle), and payment settings such as amount, tax, tip, or surcharge. Putting those settings in metadata does not configure the payment.
- Use string keys and JSON values: strings, numbers, booleans, nulls, arrays, or nested objects. Convert dates and custom application objects to JSON-compatible values first.
- Omitting metadata stores no custom metadata for that event. An empty object
{}is valid and is distinct from no value. - A refund can carry different metadata from the original sale. Later events do not automatically copy or merge the previous event's metadata. Pass the order reference again when you want it on a refund.
- Metadata is stored by Koard for retrieval. It is not a processor configuration field or a guarantee of text appearing on a receipt.
- Include business references only; do not include card numbers, CVVs, PINs, API keys, session tokens, or other secrets.
Send metadata with a sale or preauth
Use Android's org.json.JSONObject. You do not need the SDK's internal serialization types. The example assumes the SDK is initialized, the merchant is signed in, a location is selected, and the reader is enrolled and ready.
import android.app.Activity
import com.koardlabs.merchant.sdk.KoardMerchantSdk
import com.koardlabs.merchant.sdk.domain.KoardTransactionResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext
import org.json.JSONObject
suspend fun saleWithMetadata(
activity: Activity,
eventId: String,
onResponse: suspend (KoardTransactionResponse) -> Unit
) = withContext(Dispatchers.IO) {
val metadata = JSONObject()
.put("order_id", "order-123")
.put("channel", "mobile")
.put("fulfillment", JSONObject().put("pickup", true))
KoardMerchantSdk.getInstance().sale(
activity = activity,
amount = 1250L, // $12.50 in minor currency units
currency = "USD",
eventId = eventId,
metadata = metadata
).collect { response ->
onResponse(response)
}
}
Use a UUID4 for eventId and follow the idempotency guidance for retries. Handle every reader/transaction response as in Running Payments, and save the returned transactionId. This callback runs on a worker thread; switch to the main thread for UI updates.
For an authorization without capture, replace sale(...) with preauth(...); the activity, amount, currency, eventId, and metadata arguments above are the same. Run the sale or the preauth according to your payment flow.
Send metadata with a refund
import com.koardlabs.merchant.sdk.KoardMerchantSdk
import com.koardlabs.merchant.sdk.domain.KoardTransaction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
suspend fun refundWithMetadata(
transactionId: String,
eventId: String
): KoardTransaction = withContext(Dispatchers.IO) {
KoardMerchantSdk.getInstance().refund(
transactionId = transactionId,
amount = 500L, // $5.00; null requests a full refund
eventId = eventId,
metadata = JSONObject()
.put("order_id", "order-123")
.put("return_id", "return-456")
.put("reason", "customer_return")
).getOrThrow()
}
This overload is a backend refund with no tap. refundEmv(...) and the Activity-based refund(..., withTap = true) overload also accept metadata for a card-present refund and emit a transaction flow.
How it reaches Koard
For sale/preauth taps, the SDK puts your JSON object inside the transaction data it passes to Visa KiC as merchantCustomInfo. KiC delivers that context to Koard's Android authorization endpoint, which stores your metadata with the event. The SDK handles the JSON/Base64 transport encoding; pass a JSONObject, not a pre-encoded string.
For a backend refund, the SDK sends metadata in the refund request body. Omit it or pass null when no custom data is needed. Adding metadata does not change the payment amount, reader interaction, or response flow.
Retrieve and view metadata
The current SDK transaction response models do not expose a typed metadata property. Retrieve the raw transaction JSON through your backend or an API client using an API key authorized to read that merchant's transactions.
Save the returned transaction ID, then call GET /v1/transactions/{transaction_id}. The top-level metadata belongs to the current event; history[].metadata belongs to each earlier event. For one specific operation, call GET /v1/transactions/event/{event_id} using the event ID sent with that operation.
For example, from your backend or a local API client with KOARD_API_KEY set securely:
KOARD_BASE_URL="https://api.uat.koard.com"
TRANSACTION_ID="your-transaction-id"
curl --fail-with-body --silent --show-error \
"$KOARD_BASE_URL/v1/transactions/$TRANSACTION_ID" \
-H "x-koard-apikey: $KOARD_API_KEY" \
-H "Accept: application/json"
For Production, use https://api.koard.com and a production key. A transaction ID and key must belong to the same environment and authorized account scope.
A response after a refund may contain these fields (other transaction fields omitted):
{
"transaction_id": "your-transaction-id",
"event_id": "refund-event-id",
"transaction_type": "refund",
"metadata": {
"order_id": "order-123",
"return_id": "return-456",
"reason": "customer_return"
},
"history": [
{
"event_id": "sale-event-id",
"metadata": {
"order_id": "order-123",
"channel": "mobile"
}
}
]
}
If a later event has metadata: null, check the original event in history or use its event lookup; null at the top level does not mean the sale's metadata was deleted. Likewise, older transactions and requests that omitted metadata can legitimately return null.
To display metadata inside your app, have your backend retrieve it and return the fields your UI needs. Do not assume the SDK demo receipt or MMS transaction screen displays arbitrary metadata; the raw API response is the verification path described here.
Verify in UAT and Production
- Send a sale or preauth with a recognizable
order_idand retain its event ID and returned transaction ID. - Retrieve that event in the same environment and confirm the metadata values and JSON types.
- If testing a refund, pass a separate
return_idand retain the sale'sorder_idexplicitly. Retrieve the refund event and the transaction history to compare them. - Reinitializing or deinitializing the SDK does not erase metadata already stored in Koard. Log in again before continuing SDK operations.

