Appearance
Migrate payments to in-app events
Move from capturePayment to trackEvent, which sends each payment as an in-app event with revenue
In the new SDK versions, a payment is an in-app event with revenue. You send it with trackEvent, the same method you use for custom events.
capturePayment and removePayment are deprecated. They still work in the new versions and show a deprecation warning in your IDE, and they will be removed in a future major version. Apps on older SDK versions keep working with no change. Move to trackEvent when you upgrade.
Use the exact same event name, with the same spelling and letter case.
Your old payment type becomes the event name. If you sent FIRST_PAYMENT before, send "FIRST_PAYMENT" now. Do not change it to first_payment, First_Payment, or firstPayment.
Linkrunner matches the name exactly. A different spelling is treated as a new in-app event, so your existing Meta and Google mappings will not match it, and those payments will not be sent to Meta or Google.
Migrate with your AI coding agent
If you use Claude Code, Cursor, GitHub Copilot, or Windsurf, install the migration skill in your app's repo:
bash
npx @linkrunner/skills add migrate-paymentsThen ask your agent:
text
Migrate my Linkrunner capturePayment and removePayment calls to trackEvent.The agent finds every payment call, upgrades the SDK to the right version, keeps each payment name exactly as before, and asks you for the currency of your payments. Review its changes and test one payment on a debug build, as in How to migrate. See Linkrunner Agent Skills for setup.
Before and after
The old capturePayment call, then the same payment with trackEvent. The product keys are optional: Meta receives content_ids, contents, content_type, num_items, and order_id, and Google receives content_ids as item IDs. amount stays the order total.
Before (Android SDK 4.x):
kotlin
LinkRunner.getInstance().capturePayment(
CapturePaymentRequest(
paymentId = "bk_10231",
userId = "playo_u_8842",
amount = 1200.0,
type = PaymentType.FIRST_PAYMENT,
status = PaymentStatus.PAYMENT_COMPLETED
)
)After (Android SDK 5.0.0):
kotlin
// Payment: same trackEvent, payment fields in eventData
LinkRunner.getInstance().trackEvent(
eventName = "FIRST_PAYMENT", // was type, same spelling
eventData = mapOf(
"amount" to 1200.0,
"paymentId" to "bk_10231",
"currency" to "INR", // new, required
"userId" to "playo_u_8842",
"status" to "PAYMENT_COMPLETED", // optional, the default
// product details for Meta and Google
"content_ids" to listOf("court_c2_1800"),
"contents" to listOf(mapOf("id" to "court_c2_1800", "quantity" to 1, "item_price" to 1200.0)),
"content_type" to "product",
"num_items" to 1,
"order_id" to "bk_10231"
)
)From Java, call LinkrunnerJava.getInstance().trackEvent("FIRST_PAYMENT", eventData, null, callback) with the same keys. See Tracking Payments.
How to migrate
For each capturePayment call, make three edits:
- Rename
capturePaymenttotrackEvent. - Pass your old
typeas the event name, the first argument, with exactly the same spelling. - Move the payment fields into the event data:
amount,paymentId,userId, andstatuskeep their names. Addcurrency, which is new and required, for example"USD"or"INR".
Then replace removePayment calls as shown in Refunds and removePayment, and test once with a debug build. The payment should show on the Events page under the same name as before.
capturePayment | trackEvent |
|---|---|
type | the event name (first argument), same value and spelling |
amount | amount in the event data, the order total |
paymentId | paymentId in the event data, required |
userId | userId in the event data, still required. An empty string uses the user saved by signup or setUserData |
status | status in the event data, optional, defaults to PAYMENT_COMPLETED |
eventData | the other keys in the event data, such as product details |
| Not available | currency in the event data, new and required |
The event is a payment only when the event data has amount, paymentId, and currency together. If any of the three is missing, it is not a payment:
amountorcurrencywithoutpaymentIdis a normal custom event, exactly as before.paymentIdwithamountorcurrencymissing is an error, and nothing is sent.
Only PAYMENT_COMPLETED payments count as revenue and are sent to ad networks. See Payment status for the other statuses.
Old payment types
These are the old payment types. Send them exactly like this, in uppercase:
FIRST_PAYMENT, SECOND_PAYMENT, TRIAL_STARTED, TRIAL_CANCELLED, WALLET_TOPUP, FUNDS_WITHDRAWAL, SUBSCRIPTION_CREATED, SUBSCRIPTION_RENEWED, SUBSCRIPTION_CANCELLED, SUBSCRIPTION_REFUNDED, SUBSCRIPTION_BILLING_ISSUE, DEFAULT, ONE_TIME, RECURRING
If you did not pass a type before, your payments were saved as DEFAULT. Use "DEFAULT" as the event name. In Flutter, PaymentType.DEFAULT_PAYMENT was also saved as DEFAULT, so use "DEFAULT", not "DEFAULT_PAYMENT".
SDK versions
| Platform | Package | Older versions | New versions (trackEvent payments, capturePayment deprecated) |
|---|---|---|---|
| Android | io.linkrunner:android-sdk | 4.x | 5.0.0 |
| iOS | LinkrunnerKit | 4.x | 5.0.0 |
| React Native | rn-linkrunner | 3.x | 4.0.0 |
| Flutter | linkrunner | 4.x | 5.0.0 |
| Expo | expo-linkrunner | 3.x | 4.0.0, with rn-linkrunner 4.0.0 or later |
| Capacitor | capacitor-linkrunner | 2.x | 3.0.0 |
| Cordova | cordova-linkrunner | 1.x | 2.0.0 |
| Unity | Unity bridges | Bridges on native 4.x | Bridges on native 5.0.0 |
Older SDK versions
Linkrunner also records a payment sent with trackEvent from an older SDK, so payments are not lost while your users update the app. The event data must have paymentId, amount, currency, and userId, spelled exactly like that.
Older SDKs send the event as it is and do not check it on the device, so upgrade when you can:
- If a key is missing or invalid, the event is stored as a custom event. The request still succeeds and your app sees no error. New SDK versions reject the call instead.
amountmust be a number of 0 or more, andcurrencya 3-letter ISO 4217 code.statusis optional and must be exactlyPAYMENT_INITIATED,PAYMENT_COMPLETED,PAYMENT_FAILED, orPAYMENT_CANCELLED.payment_idin snake case is not read inside the event data.
Send each payment through one path only. Use capturePayment or trackEvent for a payment, not both. The same paymentId sent through both under the same name is stored once. Under different names it counts as two payments, so your revenue is counted twice.
Refunds and removePayment
removePayment is deprecated. It still works in the new versions and shows a deprecation warning, and it will be removed in a future major version. To record a refund or a cancelled booking, call trackEvent with the same event name, paymentId, amount, and currency as the original payment, and status set to PAYMENT_CANCELLED. All three payment fields are needed, or the SDK rejects the call. That payment no longer counts as revenue, and nothing is sent to ad networks.
Using a new event name
You can use a new name such as purchase instead of an old type. Before you release, add a mapping for that name in the dashboard for Meta, Google, and any other partner that should get it. Without a mapping, the payment is saved in Linkrunner but not sent to ad networks.
Keep one name per payment. The same paymentId sent under two names counts as two payments.
Sending payments from your server
Use POST /api/v1/capture-event with amount, payment_id, and currency next to event_name and user_id. The same naming rule applies. See Events Capture API. POST /api/v1/capture-payment is deprecated but keeps working.
Need help? Contact support@linkrunner.io.