
Attribution Windows for Subscription Apps: How They Work
An attribution window is the deadline for crediting revenue to a creator's click. A hold window is the delay between booking that commission and letting anyone withdraw it. Subscription apps need both, because the money arrives in instalments and some of it goes back out. Set the first one badly and creators are paid for traffic they did not send, or go unpaid for traffic they did. Set the second one badly and you pay commission on a charge that Apple refunds nine days later.
This is for the founder staring at two numbers in an app settings screen. Our defaults are 30 days and 14 days, and the rest of this explains what happens on either side of each boundary.
#An attribution window is a deadline on credit, not a memory
The attribution window is the oldest a click may be and still earn credit for what the user does next. It never deletes anything. It decides whether a recorded click is allowed to be the reason for a payment. Past the boundary the row is still there, and it earns nothing.
We treat the boundary as inclusive. An event on day 30 of a 30 day window attributes; an event on day 31 does not. That pair is a golden vector in our attribution package, because an off by one in a money rule becomes a support thread every month it survives.
Two jobs founders expect of it, wrongly. It does not pick a winner when several creators touched the same user; that is the attribution model. And it holds nothing back.
What it really controls is how much late traffic you honour. Creator traffic converts late. Someone watches a review on a Tuesday and subscribes five weeks later, and a 7 day window discards that conversion without an error, a warning or a row. The creator sees zero and concludes your program does not work.
#Why does a subscription program need a second timer?
Because a charge is not final when it settles. The card clears, the webhook fires, the commission exists, and the customer can still get their money back. The hold window is the gap between creating a commission and letting it leave, sized so that most reversals arrive while the money is still yours.
The refund pressure is real on both stores. Google Play tells buyers that within 48 hours of a purchase "You may be able to get a refund depending on the details of the purchase", and after that to "Contact the developer to troubleshoot and find out if you can get a refund" (Google Play refunds, verified 2026-09-13). Apple routes refund requests through reportaproblem.apple.com and tells customers to "Wait 24 to 48 hours for an update on your request" (Apple refund support, verified 2026-09-13).
Chargebacks are the harder half. Stripe documents that "Card networks typically allow cardholders to initiate a dispute within 120 days of the original payment", that a business then has roughly 7 to 21 days to respond, and that the issuer takes 60 to 75 days to decide, so the whole flow can run 2 to 3 months (how disputes work, verified 2026-09-13). Local payment methods such as Klarna and PayPal usually give customers up to 180 days.
No hold that long is worth offering anybody. A creator asked to wait four months for a first payment takes a flat fee from someone else. Our position: size the hold for store refunds, pay out, and handle disputes through a written affiliate clawback policy that nets a reversal against the creator's next balance. Pretending a 14 day hold covers a 120 day dispute is worse than admitting it does not.
#Which timer is running at each step of the funnel?
Three clocks run across a single conversion, and only two of them are yours to configure. The deferred match window is measured in minutes and exists because the App Store destroys the link context. The attribution window is measured in days and is checked when revenue arrives. The hold period starts only after a commission exists.
| Step | What our stack does | Timer |
|---|---|---|
| Click on a branded link | The link service writes a click with a timestamp and a claim token | Attribution window opens |
| Store redirect, then install | Nothing to record: the store drops the claim token | Deferred window is running |
| First open | `start()` claims the link, or asks the API for a deferred match on a hashed IP | Deferred window closes |
| `identify(userId)` | Binds your user id to the attribution and backfills earlier orphaned events | Attribution window still open |
| Paid event | The billing webhook is matched to the most recent attribution for that user | Attribution window is checked here |
| Commission created `pending` | `amount × rate_bps / 10000`, with the rate snapshotted | Hold period opens |
| Commission `matured` | Eligible for a payout export | Hold period closed |
The deferred step is the one people underestimate. A user sent to the App Store arrives back with no claim token, so the server matches on a hashed IP and a short window, 60 minutes by default. Ours refuses rather than guesses: two clicks in that window pointing at two different creators, a shared office or a family router, attribute nothing. Deferred deep link attribution that guesses is how a program pays the wrong person confidently.
MyAppAffiliate is an affiliate and creator attribution platform for subscription mobile apps and SaaS. It tracks affiliate links and codes from click to install, subscription, renewal and refund through RevenueCat, Adapty, Superwall, Stripe and Paddle webhooks, calculates commissions, and pays creators, with flat pricing and no percentage of revenue. Both windows are per app settings, and every provider is normalized to the same event types before either timer is consulted, described in the webhooks reference and in what each event does to the ledger.
#Last click inside a window is the honest default, and here is what it gets wrong
Last click means the most recent valid attribution wins, and it is the default because it is the only model a creator can verify. A creator can see their click, see the timestamp and see the subscription. Fractional models spread credit across touches nobody outside your database can reconstruct, which turns every payout question into a trust exercise.
Our engine picks the attribution with the latest first open that is still inside its window, and breaks exact ties by record id so the same inputs always produce the same payment. That determinism matters more than the model choice. A program that occasionally pays a different creator for identical inputs cannot be defended in an email.
Three failure modes we accept knowingly. A long form review that created the demand loses to a deal roundup the buyer tapped last. A creator whose audience converts in a single session looks better than one whose audience thinks for a month, on the same window. And a second link tapped on day 29 takes that first charge and every renewal after it, because our window is evaluated against each event rather than only the install.
What we have not measured: how often the last click and the demand creating click differ across app programs. We see it in individual accounts and have no distribution worth quoting. Until we do, last click with a window you set deliberately beats a model you cannot explain to the person being paid.
#What do both timers do to a real $9.99 ledger?
One customer, a $9.99 monthly plan, a 20% commission, three renewals and a refund on the second charge. The app is set to a 365 day attribution window and a 14 day hold. Commission per charge is 999 × 2000 / 10000 rounded, which is 200 cents.
| Day | Event | Gross | Commission | Matures | Outcome |
|---|---|---|---|---|---|
| 0 | Click on the creator's link | Attribution window opens | |||
| 1 | First open, then `identify()` | Attribution written, window closes day 366 | |||
| 3 | `purchase`, first month | $9.99 | $2.00 | day 17 | Matured, then paid |
| 33 | `renewal` | $9.99 | $2.00 | day 47 | Reversed on day 41 |
| 41 | Refund of the day 33 charge | -$9.99 | -$2.00 | Inside the hold, so nothing was paid | |
| 63 | `renewal` | $9.99 | $2.00 | day 77 | Matured, then paid |
| 93 | `renewal` | $9.99 | $2.00 | day 107 | Matured, then paid |
| **Net** | **$29.97** | **$6.00** |
Four charges at $9.99 is $39.96 billed, minus the refunded $9.99, so $29.97 stays. Four commissions at $2.00 is $8.00 booked, minus the $2.00 reversed on day 41, so $6.00 is paid. Check it against the headline rate: 20% of $29.97 is $5.994, so the creator keeps six tenths of a cent more than the stated rate, because each charge rounds 199.8 up to 200. Rounding per charge in the creator's favour is a choice, and it is cheaper than explaining a $0.006 discrepancy.
The refund is the whole argument for the second timer. It landed on day 41, eight days into the 14 day hold on the day 33 commission, so that commission reversed while still pending. With a 7 day hold, the $2.00 matures on day 40 and is exportable a day before the refund arrives.
#How long should each window be?
Longer attribution windows suit considered purchases and annual plans; longer holds suit categories that refund a lot. These are the values we would set, not measurements. Treat the table as a starting point you revise once you can see where your own conversions actually land.
| App type | Attribution window we would set | Hold period | Why |
|---|---|---|---|
| Impulse consumer app, monthly plan, paywall on first open | 14–30 days | 7–14 days | Conversion is same session or never, and store refunds land within days |
| Considered purchase, annual plan, long evaluation | 60–90 days | 14–30 days | Buyers compare for weeks, and one annual charge is a much bigger refund |
| AI or utility app with a high refund rate | 30 days | 30 days | Refund pressure dominates the risk, not late conversion |
| Web SaaS billed on Stripe | 30–60 days | 30 days | Disputes run far past any workable hold, so lean on the clawback policy |
| Any program paying commission on renewals | 365 days | 14 days | Our window is checked on every event, so renewals need it left open |
The last row is the one that surprises people, and it is why we say to read the setting as "how long this creator keeps earning" rather than "how long a click stays alive". Trials shift the arithmetic again, because a $0 trial start counts in the funnel and earns nothing until it converts, which is covered in do affiliates get paid on free trials.
Hold periods have a floor set by your billing stack, not by policy. RevenueCat delivers most webhooks "within 5 to 60 seconds of the event occurring" but notes cancellation events usually take around 2 hours, and it retries a failed delivery up to 5 times at 5, 10, 20, 40 and 80 minutes (webhooks, verified 2026-09-13). A refund that arrives as a CANCELLATION carrying cancel_reason: CUSTOMER_SUPPORT is described in their reference as "A subscription or non-renewing purchase was canceled or refunded" (event types and fields, verified 2026-09-13). A hold shorter than a couple of days is a hold that races your own webhooks.
#An attribution window is not what SKAdNetwork measures
Store attribution frameworks answer a different question from an affiliate program, and their timers are not yours. They tell an ad network that a campaign produced installs. They cannot tell you that a specific creator produced a specific subscriber, which is the only fact a commission can be computed from.
Apple's own description makes the boundary clear. Advertisers "can receive a postback within 24-48 hours of a user launching the app", the postback carries a "Source identifier the ad network provided when displaying the ad", and the publisher item id "is conditional and only displays when privacy thresholds are met", with conversion values gated the same way on "crowd anonymity thresholds" (Apple ad attribution, verified 2026-09-13). Apple also states plainly that you "may not derive data from a device for the purpose of uniquely identifying it".
Read that as a program operator and three things follow. The unit is a campaign, not a person, so there is no user id to join a renewal to. The detail you receive depends on volume, so a creator sending 40 installs a month may sit permanently under the threshold. And the postback is a one time install signal, while a subscription affiliate program is paid on month 14 as well as month 1.
So we run first party attribution against your own user id instead: one attribution engine across mobile and web, joined on the string you pass to identify(). An MMP is the right tool for buying paid media. Neither it nor SKAdNetwork was built to pay a named creator on a renewal three months out.
#Six decisions produce your two window values
Work through these in order. Each one narrows the next, and the whole exercise is an afternoon with your billing data open.
- Measure your own click to paid lag. Pull the last 90 days of conversions and find the 90th percentile. Not the median: the window exists for the tail.
- Decide how long you pay on renewals. First charge only, twelve months, or the full lifetime. This sets a floor on the window, because our engine checks every event against it.
- Take the larger of those two numbers. Round up to something round enough to state in a creator agreement without a footnote.
- Count your refunds by age. Days between charge and refund, from your billing provider's export. The hold should cover most of that distribution, not all of it.
- Subtract what creators will tolerate. Anything past 30 days costs you recruitment. Write the difference into the clawback clause instead of the hold.
- Set both, then re-run one real conversion. Recompute the ledger by hand the way we did above and check the dashboard agrees before a creator ever sees it.
Step 5 is the trade nobody enjoys. A longer hold is safer for you and worse for the person deciding whether to promote your app, and that tension only gets priced. We put the same reasoning behind flat pricing rather than a revenue share: the operator should carry the variance, not the creator. Payout mechanics after maturation are in commissions and payouts for subscription businesses, and our own plan limits are on pricing.
What happens when an attribution window expires?
The click record stays; it stops being allowed to earn. A purchase after the boundary produces a normalized event, no matched attribution, and no commission. Nothing errors and nothing appears in the creator's dashboard, which is why an expired window looks identical to traffic that never converted.
Is the attribution window the same as the hold window?
No. The attribution window decides whether a click may be credited for a paid event at all. The hold window decides how long a commission that already exists sits as `pending` before it can be exported to a payout. One governs credit, the other governs cash.
Do subscription renewals have to fall inside the attribution window?
In our engine, yes. Every commissionable event is checked against the window on the attribution record, so a renewal on day 45 of a 30 day window earns nothing. If you intend to pay recurring commission for a year, set the window to cover that year.
How long should the hold period be for an iOS app?
We default to 14 days. Google Play tells buyers they may get a refund within 48 hours and to contact the developer after that, and Apple routes refunds through reportaproblem.apple.com. Two weeks catches the bulk of store refunds without making creators wait a month.
Does a hold window protect against chargebacks?
Barely. Stripe documents that card networks normally let a cardholder dispute within 120 days of the payment, and local payment methods often allow 180. No hold that long is worth offering a creator, so hold covers refunds and your clawback policy covers disputes.
#Set both windows before you recruit your first creator
Open your app settings and write down what the two numbers mean in one sentence each, in the words you would use with a creator. If you cannot finish the sentence "you keep earning for ..." without checking the code, the window is wrong. Then run the ledger above against one real customer of your own and confirm the dashboard matches your arithmetic. The full setup, from link to first paid renewal, is in the RevenueCat affiliate tracking guide.