The moment you open a web store, part of your revenue leaves the app, and with it leaves the attribution your MMP gave you for free. Web purchases don't show up in LTV, ROAS looks worse than it is, and the UA team starts asking why the web store is "losing money". None of that is real; it's a reporting gap. This page closes it with the data an Appside store already sends you.
Why web purchases go missing
Your MMP SDK lives in the app. It sees installs, sessions and in-app purchases because they happen in the process it's part of. A web store purchase happens in a browser, through a checkout the SDK can't see. Unless something sends that purchase to the MMP as an event tied to the same player, the player's LTV stops at their last in-app purchase, and every campaign that drove web purchases looks like it drove nothing.
What Appside gives you
Three things, and you already have all of them if your store is live.
1. The player is known at every step. The store opens with a player token signed by your backend. It carries your player ID, so no web purchase is ever anonymous.
2. Every paid order is a signed webhook. order.paid reaches your backend with the order ID, player ID, pack, credits, price and currency, and order.refunded carries the same order ID (webhook reference). The webhook is the proof of payment; it's also the attribution event.
3. Promo codes and offers are per channel. A code per campaign, creator or newsletter, with limits and a window, gives you channel attribution even with no MMP at all.
The integration: one webhook handler
Your backend already handles order.paid to grant the pack. Attribution is one more step in the same handler.
// Your server, on the signed order.paid webhook
app.post('/appside/webhook', verifyAppsideSignature, async (req, res) => {
const { order_id, player_id, order } = req.body;
if (await alreadyGranted(order_id)) return res.sendStatus(200);
await grant(player_id, order.sku, order.credits); // exactly once per order
const player = await players.get(player_id); // your record: install id, campaign, MMP device id
await mmp.sendEvent({ // your MMP's server-to-server API
appId: player.appId,
deviceId: player.mmpDeviceId,
eventName: 'web_purchase',
revenue: order.price / 100,
currency: order.currency,
customerUserId: player_id,
eventValue: { order_id, sku: order.sku, channel: player.lastChannel }
});
res.sendStatus(200);
});
The MMP side is its server-to-server events API: AppsFlyer, Adjust, Singular and the rest each have one, keyed to the device or customer user ID your app's SDK already reported. Because the event comes from your server with your keys, it works with whichever MMP you use, and switching MMPs doesn't touch the store.
Handle order.refunded the same way with a reversal event, so refunded web revenue leaves LTV too.
Channel attribution without an MMP
If you don't run an MMP, or the question is "which channel", not "which ad", you don't need one:
| You want to know | Do this |
|---|---|
| Did the newsletter work? | One code in the newsletter; count orders in your webhook log that used it |
| Which creator converts? | One code per creator, each with a total limit |
| Does the in-game link beat push? | Open the store from each with a different offer; compare paid orders per player who saw each |
| Is the web store cannibalising in-app? | Compare the player's in-app purchases before and after their first web order, by player ID |
The last row is the one your finance team will ask about. It's answerable because the player ID is the same on both sides.
What to record
Keep a table per order with: order ID, player ID, pack, price, currency, paid at, refunded at, the code or offer used, and the channel your own system says the player came from. That table is your source of truth for web revenue; the MMP and your dashboards are views on it.
Appcharge's approach, for comparison
Appcharge announced a built-in AppsFlyer integration on June 2, 2025: a server-to-server feed of login and purchase events from its web stores to AppsFlyer, enabled by asking your account manager. If you're on AppsFlyer and happy to wait for that, it does the forwarding for you.
On Appside the forwarding is the handler above, which you write once and own. It works with any MMP, with a warehouse, or with nothing but your own order table; it includes refunds; and it doesn't need anyone to switch it on.
Create your store free. No card needed to start; 3.9% per successful payment.