Making A Living (Barely) On The iPhone App Store (aka The Numbers Post)

The App Store is a very hit-driven environment. A few apps sell a large amount of units, and the great majority sell next to nothing. That’s somewhat similar to the music industry, except that the audience for music is much larger, so both the big hits and the small players get more sales. We’ve drooled over the numbers chart toppers sold, I’ve seen sales reports of very successful games, and we’ve also seen what happens when apps languish at the bottom.

I want to share the sales data for Flower Garden. Not just the raw data, but a bit of the story behind it, my thoughts, struggles, and why things happened the way they did.

The first thing that you’ll notice is that Flower Garden is a strange in-between app. It’s far from being very successful or being at the top of any chart, but at the same time it probably made more money than 99% of the apps on the App Store. It was also relelased on April 10th, so this represents 10 months of data, an age after which most apps are usually on drip support. So this should be an interesting new data point.

The Full Monty

I know that if I put it off, you’re just going to skip to the end to see the sales plot, so let’s get that out of the way. Here it is.

Full

The vertical axis is daily profit in US $ (after Apple’s 30% cut). Flower Garden generated a bit over $21,500 over a period of 10 months. I would hardly consider that an entry-level salary, much less in California, but it’s enough for someone without a family or mortgage to (barely) make a living. I supplemented that with some teaching, writing, contracting and consulting, so overall I managed do fine and even save a bit of money last year, all while being totally indie and having full creative control over what I did every day. I’m not getting rich, but it’s not a bad life, really.

How many hours of work does Flower Garden represent? It’s really hard to say. It was 6 solid months of work from conception to release, but then there was the fuzzy time spent on marketing, updates, support, new features, etc. I would estimate it was a total of 8 months of full time work, which, at 50 hours per week, makes it 1600 hours. So that’s a depressing $13.44/hour. At least it’s over minimum wage! 🙂 On the positive side, Flower Garden should continue to sell in the foreseeable future, so that amount will go up a bit over time.

There’s clearly a story behind that graph. It’s not the usual exponential drop off you expect from most (unsuccessful apps) and shows how an aging app can pick up steam on its own after many months on the store, without ever being featured by Apple.

Launch

Let’s start from the beginning. Release day!

Beginning2

After six hard months of work, I submitted Flower Garden to Apple sometime at the beginning of April, and on April 10th I was surprised to see it had been approved. It caught me a bit off guard without my marketing campaign in place, but I announced it high and wide on this blog, Twitter, Facebook, and I even spammed all my friends with an email. The results are the area marked as (A): About $50-$60 in revenue per day (so about 25-30 sales). Not bad considering that was mostly my friends taking pity on me and people randomly seeing it on the new releases list, but it was far from an auspicious start.

I also contacted all the media sites I knew with a press release and promo codes to entice them to write a review. I was lucky that many reviews appeared over the next couple of weeks, but unfortunately they were all spread out, minimizing the PR effect. The biggest effect was when Flower Garden was simultaneously covered on TouchArcade and MacRumors. That’s what caused the big sales spike (B). From there, it was a standard exponential drop off, until, on the last day of the month, just three weeks after launch, revenue dropped below $100/day again. If that was all there was to it, Flower Garden was a big flop and I should start dusting off my resume.

Mother’s Day

I wasn’t ready to throw in the towel yet. I had a couple cards up my sleeve that I was hoping would change things around. After all, May 5th is Mother’s Day in the US. What better time to do some promotion and get Flower Garden noticed?

I decided to run a contest and give away $100 in real flowers to the winner at the same time I put Flower Garden on sale for $1.99 for the week of Mother’s Day. The result? The revenue dip you see in (A).

Middle_long

Mother’s day is probably the small, second spike in that period, but overall, that week was a loss. Lesson learned: Don’t make a sale unless your app is in a visible position (on a top chart somewhere). Flower Garden was nowhere to be seen, so the sale had no effect other than to cut profits by 33%.

I released a couple updates and did the trick updating the release date to get on the new releases list (which no longer works) and I got a couple minor spikes with (B) and (C). Subsequent updates later that summer had almost no effect anymore on sales. The trend was worryingly clear as profits dipped as low as $10/day in mid June (I have no idea what happened that day, but it was the lowest day ever for Flower Garden).

App Treasures

In early June we launched App Treasures. App Treasures is a label for indie iPhone game developers with top-quality games, and one of the main tools we’re leveraging is some cross-promotion for our games, both through the web site, and from an in-game view liking to each other’s games. Initially there was no measurable effect, but then Harbor Master was released and shot all the way to the #2 position on the US store and held its position on the top 50 for a while. The results is the area under (D). It definitely brought in some sales to Flower Garden and reversed the downward trend.

Unfortunately all good things come to an end, and referrals from Harbor Master dropped off and its slipped down the charts, and Flower Garden sales continued sinking.

Facebook and Lite

At this point I had to face reality. Was it time to give up on Flower Garden and move on to another project? I was ready to do that, but every day I would get several emails from users saying how much they loved Flower Garden, how much happiness it was bringing to their lives, etc. Some of the stories were really touching.

At the same time, every time I would show the game to someone, they usually really liked it. Not liked-it-because-I’m-there, but really, genuinely liked it. Why weren’t more people buying it then? Two problems: First, screenshots were not conveying how cool growing, animated flowers you could touch were, and second, most people didn’t even know Flower Garden existed. It had never been featured by Apple on the App Store, and the audience I was trying to reach didn’t read TouchArcade or other iPhone review sites.

It was at this point that I decided to give it one more try. My goal wasn’t adding more features as I had done with the updates so far, but to make Flower Garden more visible. I wanted more people to know that Flower Garden existed.

The first thing I did was to add Facebook integration. Not only could you send bouquets through email, but you were able to send them directly to your Facebook friends. The advantage of that approach from my point of view is that all your friends also saw the flowers so for every bouquet sent on Facebook, hopefully dozens or hundreds of people were being exposed to Flower Garden. The result on sales: Not much. Maybe it made the early part of July a little higher than it would have been otherwise, but no noticeable difference.

The second approach was to release a lite version of Flower Garden in early September. I was confident that Flower Garden was a good app, and I was hoping that once people had a chance to try the lite version, they would purchase the full version. Fortunately I was right and the effect on sales was very noticeable, pretty much doubling sales (E), but it never really took off in any significant way, and sales slowly declined over time.

Second Wind

You’d think that I would give up at this point, wouldn’t you? And I don’t say that with pride. I mean, it probably would have been smarter to quit a long time ago. But somehow, every time I was ready to move on , something else would come up that would entice me to try something else with Flower Garden.

This time it was in-app purchases (IAP). Apple had announced IAP back in June. They seemed like a very natural fit for Flower Garden, but given how few units Flower Garden had sold, I would have a very limited audience for IAP. A small percentage of a small number is a tiny number! 🙁 However, in late October Apple announced that IAP were finally allowed from free apps as well. That encouraged me to give Flower Garden one… last… try…

To be totally honest, I wasn’t expecting very much. Even including all the units of the Lite version out there, there just weren’t that many units. Especially not that many people using it on a daily basis (I’m sure a lot of the free ones were downloaded and quickly forgotten). But I thought it would be a good experience and if I only spent a week on it and made $1,000 I could call it even.

End

The result was totally unexpected. The Flower Shop opened on December 6th and revenue immediately shot up (A). On December 21st I released a new IAP called Seeds of Winter with 8 new winter-themed flowers, and people loved it and immediately purchased it. Christmas day came and went with somewhat of an increase in sales (C) but nothing really spectacular. Finally, to wrap up the year, there was a big spike on New Year’s Eve (D) (do people send flowers on New Year’s Eve? Really?).

Afterwards revenue flattened out, but at a much higher amount than before. Before IAP, daily revenue was about $50/day. Now it’s more around $180/day. That’s totally beyond any of my expectations!

But hang on, where is the revenue coming from? There are three possible sources: You can purchase Flower Garden, you can make IAP in the full version, and you can also make purchases in the free version. Here’s the breakdown:

End_broken_down

It looks like sales for Flower Garden (blue) continue to be more or less the same, with a slight increase after Christmas. The IAP from the full version of Flower Garden (green) account for most of the extra revenue, especially at the beginning. But it’s very interesting that the purchases coming from Flower Garden Free (orange) are steadily increasing and, as of last week, they became almost as large as the ones from the full version.

That can be explained because more and more people are getting the free version and upgrading it instead of buying the full version (which is exactly what I was hoping for). What’s also really interesting is the downloads of Flower Garden Free.

fg_free

Flower Garden Free was never a big player. It had the usual big initial spike, but then it settled down at around 100-200 downloads per day (which is very few considering there were 50-60 purchases per day of Flower Garden Full during that period). But, as soon as the in-app Flower Shop was released, downloads started climbing, and on Christmas day they went through the roof (relatively speaking). So it’s no surprise that IAP from Flower Garden Free picked up in these last few weeks.

The Future

I have no idea what the future will hold for Flower Garden. This week Apple selected Flower Garden and featured it on the App Store as a Staff Favorite worldwide (that period is not show in the sales graphs). I’m also already preparing a new update and some more IAP for Valentine’s Day, so I’m sure there will be more ups and downs in the near future. I’d certainly like to continue supporting Flower Garden for as long as it’s profitable.

I’ll follow up this post in a couple of months with the aftermath of the App Store feature and Valentine’s Day. Also, stay tuned for another post with more details of the IAP, what’s successful, purchase patterns, and more.

Flower Garden Now An Apple Staff Favorite On The App Store Worldwide!

Aparently Santa came to town a little later this year. Or maybe it was the Reyes Magos (also bit late). In either case, I woke up this morning to find a very nice present waiting for me at my computer: Apple featured Flower Garden as an Apple Staff Favorite! Apparently this is in all App Stores worldwide too (I have confirmations for Canada and Thailand, so I imagine it applies to other territories as well).

fg_appstore_feature

This is the first time Flower Garden has received any kind of “Apple love”, so it’s particularly exciting, especially considering that Flower Garden was released all the way back in April. I imagine the recent update, which included the Flower Shop with in-app purchases, must have caught someone’s eye.

The only downside is that the featuring might throw off the sales data I was collecting to show the effect on sales that in-app purchases and the holiday season had. But I’m not really complaining. That’s a very nice reason to have the data thrown off 🙂 Thank you Apple!

All About In-App Purchases Part 4: iTunes Connect

We’re used to going through the approval process for binaries, but in-app purchases are a bit different. Here’s some of what I learned going through this for Flower Garden.

Purchase States

When you first create an in-app purchase in iTunes Connect, it is listed as “Pending Developer Approval”. This means that if you ask for info on that product id with a SKProductsRequest, you’ll get the info for the item only on development builds, not in distributions builds.

Once your in-app purchase item is ready, you need to upload a screenshot of it working in the app, and approve it yourself. At that point it enters the queue to be approved by Apple. Once an item has been approved by Apple (marked as Ready for Sale), it will appear both in development and distribution builds.

This behavior is very convenient because you can add in-app purchases normally for development, and not worry about them showing up for all your existing users until they’re done. Just make sure to keep around a distribution build of your app to test what the end user sees.

In addition to that, you can flag items as available for sale or not. That way you have the added flexibility of temporarily removing an item from sale, or making it available at a later time.

itunesconnect

Approval Process

The first time you submit any in-app purchases, you’ll probably do it along with a binary update (since you need to code in support for displaying and allowing the purchases of items). You’ll have an option to mark the items to be approved along with the binary, so everything will become available on the App Store at the same time. In that case, the approval process is just like for a regular binary update (it seems to be averaging a week lately).

You can also submit in-app purchase items by themselves, without an app binary update. I did this for the Seeds of Winter pack in Flower Garden. Somehow, I was expecting a turnaround time of a day or two. After all, I figured they would look at the provided screenshot and approve it. Unfortunately, that wasn’t the case. In this particular situation, approval took almost a week, and I’m not sure it was because of the normal process, or because I emailed Apple asking what the procedure was to submit a product by itself (the product was approved within an hour of me getting a response from them saying they would look into it).

I was monitoring iTunes Connect pretty obsessively at the time it was approved (because it was right before my two-week break for the holidays) and I saw that the Seeds of Winter pack went from “Waiting for Review” to “In Review” to “Approved for Sale” in a matter of two minutes. That was one quick check! (I’m not complaining though).

What’s even more interesting is that I didn’t know that an item wouldn’t appear on a distribution build unless it was approved by Apple, so I had added an extra layer on the shop catalog file on my server that allowed me to prevent an item from showing up on the Flower Shop until I turned it on myself. And since that check wasn’t turned on, it means that Apple approved the in-app purchase by looking at the provided screenshot, without running it in the app.

This shouldn’t be too surprising. After all, I’m not even sure what the point is for in-app purchase approvals. For content that comes from the server, I can change it at any time, so the only thing they can approve is the name and description provided in iTunes Connect. I suppose the screenshot is fine for that, but I would also expect really speedy approval times. After all, not having to wait for lengthy approval processes is one of the big draws for in-app purchases.

I did learn one important lesson from going through this process. Next time I submit a binary prepared for more in-app purchases, I’m going to create stand-ins for all possible in-app purchases I’m considering and submit them for approval at the same time as the app binary, but marked as not ready for sale. That way they will all be approved at once, and I can take my time creating the final version for those items and making them available instantly whenever I want.

Purchasing Items

The same way that non-Apple approved items only show up in development builds, the only accounts that can purchase any items on development builds are test accounts using the sandbox environment. These are accounts you create on iTunes Connect and allow you to purchase items in your programs without spending real money (otherwise I’d be in trouble considering how many times I ended up purchasing items during the development of the Flower Shop!). Conversely, distribution builds only work with non-test accounts and non-sandbox environment.

One thing that threw me off was that I started getting support requests from people who had trouble logging in with their account to make in-app purchases. They claimed they were being asked about this “sandbox environment”. That threw me in a panic for a second, because I thought that everybody was somehow accessing the sandbox environment and nobody could make any purchases (because they had full iTunes accounts). It turns out, that’s a problem that happens with jailbroken phones with a particular app installed. It’s nicely documented here so I’ve been pointing people in that direction.

Finally, I believe there’s no provision in iTunes Connect to deal with different binary versions, but that’s something that’s very important to handle. If I release a new binary with some new feature built into the app, and an in-app purchase to unlock it, I don’t want people with an earlier version purchasing it and getting mad that it’s not working. Ideally this should be an option in iTunes Connect, but in the meanwhile, I build a version check into the shop catalog itself. That way only apps with that version or higher can even display a particular item. Or I can even take it a step further and display the item but notify the user that they need to update the app before the purchase it.

All About In-App Purchases Part 3: Anti-Piracy Measures

One of the big advantages of in-app purchases for some people is that it makes pirating content more difficult. For me it wasn’t much of an issue because a) Most of the people who enjoy Flower Garden aren’t the ones checking out the app warez sites, and b) I know better than to waste my time trying to prevent piracy [1]. Besides, I always roll my eyes whenever I hear that 90% or some other large percentage of the users are using a pirated copy, so the developer lost all that money. News flash: 99% of those users using a cracked version wouldn’t have bought your product anyway, so you can rest a bit easier at night.

Even so, I decided to take a few extra steps that at least will make things a bit more difficult to crack and keep things more secure. I could have gone way beyond this, but I don’t think that would have been worth my time.

Verify the purchase

fg_pirateRight now, you can download programs that will automatically crack an app by extracting its data and resigning it so it works on any jailbroken phone. There’s no doubt that even if you implement in-app purchases just like I described in earlier posts, crackers will have to analyze the code and do some steps by hand, so that might be enough to slow down the pace of cracked apps. Even so, an experienced cracker won’t take long to crack your in-app purchases unless you take some extra steps. All they have to do is find the function that you call in response to getting the go ahead from the App Store and they’ll gain access to all content.

The first thing you should do is to verify that the purchase happened correctly. To do that, once you get the notification in your app that the payment was complete, you send the receipt to your server, and have your server verify it in turn with the App Store. Since crackers don’t have access to your server (if they do you’re in more trouble than a few cracked apps), that step is secure. If your app was cracked and the user didn’t pay for the purchase, the App Store will fail the verification of the receipt and you don’t have to deliver the content.

How do you check that the receipt is valid? First of all, you need to send the receipt of the purchase to your server. I send it along with some other info, like the user’s UDID, what product was purchased, etc so I can keep general usage statistics. The relevant bits of code are the following:

NSString* receiptString = [self createEncodedString:transaction.transactionReceipt];
//...
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[@"Content-Disposition: form-data; name=\"receipt\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[receiptString dataUsingEncoding:NSUTF8StringEncoding]];
//...
[req setHTTPBody:postBody];

The createEncodedString function takes some arbitrary data and creates a base64 encoding. That’s one of the parts that is not particularly well documented in the SDK documentation.

- (NSString*) createEncodedString:(NSData*)data
{
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    const int size = ((data.length + 2)/3)*4;
    uint8_t output[size];

    const uint8_t* input = (const uint8_t*)[data bytes];
    for (int i = 0; i < data.length; i += 3)
    {
        int value = 0;
        for (int j = i; j < (i + 3); j++)
        {
            value <<= 8;
            if (j < data.length)
                value |= (0xFF & input[j]);
        }

        const int index = (i / 3) * 4;
        output[index + 0] =  table[(value >> 18) & 0x3F];
        output[index + 1] =  table[(value >> 12) & 0x3F];
        output[index + 2] = (i + 1) < data.length ? table[(value >> 6)  & 0x3F] : '=';
        output[index + 3] = (i + 2) < data.length ? table[(value >> 0)  & 0x3F] : '=';
    }    

    return  [[NSString alloc] initWithBytes:output length:size encoding:NSASCIIStringEncoding];
}

That just sends the receipt to my server. Once the server receives that request, it needs to verify the data with the App Store. The exact format of the App Store request is, again, not well documented. It turns out it needs to be sent as anonymous data in a POST request. After some trial and error, this is the php code I’m using in my server to verify the receipt:

function isReceiptValid($purchase)
{
	$data = array ('receipt-data' => $purchase->receipt );
	$encodedData = json_encode($data);

	$url = "https://buy.itunes.apple.com/verifyReceipt";

	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_POST, true);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedData);
	$encodedResponse = curl_exec($ch);
	curl_close($ch);

	if ($encodedResponse == false)
	{
		logError($purchase, "Payment could not be verified (no response data).");
		return false;
	}

	$response = json_decode($encodedResponse);
	if ($response->{'status'} != 0)
	{
		logError($purchase, "Payment could not be verified (status != 0).");
		return false;
	}

	$purchase->storeReceipt = $response->{'receipt'};
	return true;
}

If things check out, the server returns a valid code, and you can go ahead and make the content available to the user. Otherwise, it means the user is trying to pass up a fake purchase as valid and you can deal with it any way you want.

Get content from the server

Of course, if the content is already embedded in the app itself, the cracker can bypass both the App Store purchase and the receipt verification through your server, and just access the content directly. If you want a better chance of not being cracked, have your server deliver the content. That way, if the receipt doesn’t check out, you never transfer the content.

In the case of Flower Garden, all seed packets are downloaded from the server, but the other purchases (extra pots, extra fertilizer) are just changing a few internal variables, so they would be a lot easier to crack.

One of the easiest ways that crackers can get around this verification is by buying the content once and saving the receipt that comes with it. Then, cracked versions can pass that receipt to your server, they’ll be validated as correctly, and they’ll be able to download the content. So, as an added precaution, you might want to keep track how many times a certain purchase is re-downloaded, especially from different UDIDs. I can’t imagine a legitimate reason for a user to download the same purchase more than 20-30 times from different devices, so denying the purchase at that point in the server seems justified.

Save those receipts

The last step I to make cracking more difficult was to save the receipt itself to disk along with the content that was downloaded. Then, periodically, the game can pick one of those receipts, and verify with the server that it’s still valid. If it’s ever reported as an invalid receipt, the game deletes the content for that purchase. This is not as draconian as it sounds, because even if it’s ever triggered for a legitimate user (and I have no idea how that could be other than data corruption), they can always re-purchase it again for free (and they would want to do that to fix their data corruption anyway).

To be able to delete things easily and cleanly, I keep each downloaded in-app purchase on a separate directory. So it’s very easy to delete the whole directory without having to access files everywhere. The hardest thing is making sure the game deals with the missing data and that nothing is referencing it. I’ll touch on that on a future part of this article.

Keeping purchases in separate directories also makes it easier to reset all purchases. That’s something I didn’t originally include in Flower Garden, but I should have. Sometimes, data can get corrupted or maybe a bug occurs because of some combination of purchases (hopefully not, but you never know!). Allowing the user to reset the purchases and re-download them allows them to fix those problems without having to reinstall the app and lose their progress.

[1] I know from first hand experience that one dedicated teenager with all the time in his hands can spend inordinate amounts of time bypassing the most elaborate anti-piracy schemes. The harder it is, the more of a challenge it becomes, and the more fun it is. To this day, I still remember the thrill of finally bypassing the copy protection of 1943 on the Amstrad CPC after two days of non-stop hacking, which included several loops XORing all the available memory (including the currently executing code), and messages from the developers taunting me!

Great Presentation on Data-Oriented Design

Memory CPU gapA few days ago, Tony Albrecht posted the slides of his presentation titled “Pitfalls of Object-Oriented Design” [1]. Even though the title is really broad and could easily be misinterpreted, it’s not just a general bash on OOD. Instead, it’s very much focused on how object-oriented design is not a good match for high-performance apps (games) on modern hardware architectures with slow memory access and deep memory hierarchies. His proposed solution: Data-oriented design. Spot on!

If you haven’t seen the presentation, go download it right now. It’s really well put together and he has some great detailed examples on how caches are affected with traditional object-oriented design vs. a more data-oriented approach.

[1] Thanks to Christer Ericson for pointing that out.