GUIDE / WEB STORE ATTRIBUTION

Web store attribution: from campaign to paid order

How to attribute mobile game web store purchases to the campaign, channel or creator that caused them, with the data your store already gives you: a signed order webhook with player ID and pack, promo codes per channel, and a server-to-server event to your MMP from your own backend.

Updated

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.

Frequently asked questions

Why are web store purchases missing from my attribution?

Because they happen in a browser, outside the app your MMP SDK is installed in. Unless the web purchase is sent to the MMP as a server-side event tied to the player, it never joins the install and campaign data.

How does Appside support attribution?

Every paid order reaches your backend as a signed order.paid webhook with the order ID, player ID, pack, price and currency. Your backend knows the player's install and campaign, so it forwards a purchase event to your MMP or warehouse with both. There's no built-in MMP connector; the integration is yours, which also means it works with any MMP.

Can I tell which channel a web purchase came from without an MMP?

Yes. Give each channel its own promo code or offer, and open the store from your own links. The order webhook plus your own record of how the player arrived is enough for channel-level attribution.

Does Appside send events to AppsFlyer or Adjust?

Not directly. Appside sends the order to your backend; your backend sends the event to the MMP using its server-to-server API, with your own keys. Appcharge has announced a built-in AppsFlyer integration (June 2, 2025); on Appside the same result is one webhook handler you control.

What about refunds?

The order.refunded webhook carries the same order ID, so you can send a negative or reversal event to the MMP and keep LTV honest.

Sources

Related

SEE IT YOURSELF

Build your store
before the first call.

Try the live demo ↗Explore Studio
LET’S TALK

Book a demo

Connect with Oded to arrange a walkthrough of Appside.

Contact Oded on LinkedIn ↗