Offers and promo codes

Set up introductory, promotional, win-back and offer-code pricing, target who sees each one, and track every redemption back to the customer.

CashSDK models four kinds of discounted pricing, matching what Apple and Google let you sell:

KindWhat it isWho gets it
introThe first-time price on a subscription, including a free trialAnyone who has never bought in the group
promotionalA discount you present to a current or lapsed subscriberWhoever you target
win_backA discount aimed at customers whose subscription endedWhoever you target
offer_codeA redeemable code: App Store offer codes, Play promo codesWhoever you give the code to

Set up an offer code#

1
Create the code at the store

App Store Connect: your subscription, Subscription Pricing, Offer Codes. Create a code group, set the discount and how many redemptions it allows, then download the one-time-use codes or copy the custom code.

Play Console: Monetize, Promo codes. Create a promotion for the product, choose one-time or custom, and generate the codes.

2
Describe it in CashSDK

Dashboard, your app, Offerings, Offers, Create offer. Pick the product, set the kind to Offer code, and put the store's own offer id in identifier. That id is the join key: it is what the store stamps on the transaction, and how a redemption finds its way back to this row.

Or over the API:

bash
curl -X POST https://api.cashsdk.com/v1/apps/$APP_ID/catalog/offers \
  -H "Authorization: Bearer $CASHSDK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "productId": "prod_...",
    "kind": "offer_code",
    "identifier": "LAUNCH50",
    "displayName": "Launch week, 50% off",
    "paymentMode": "pay_as_you_go",
    "discountPct": 50,
    "duration": "P1M",
    "periodCount": 3
  }'
3
Hand the code out

Offer codes are redeemed at the store, not in your app: Apple opens a redemption sheet (or apps.apple.com/redeem), Play takes the code at checkout. Nothing has to change in your app for a code to work.

Use the store's id verbatim. An offer whose identifier does not match what the store stamps still works for the customer, but its redemptions arrive unmatched: recorded, attributed to the customer, and not linked to your row.

Target a promotional or win-back offer#

Promotional and win-back offers carry an audience, and CashSDK evaluates it with the same matcher the device path runs. Before you save one, the dashboard tells you how many real customers it currently targets:

bash
curl -X POST "https://api.cashsdk.com/v1/apps/$APP_ID/catalog/offers:preview-audience" \
  -H "Authorization: Bearer $CASHSDK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"audience": {"conditions": [{"key": "expired_within_days", "value": [0, 30]}]}}'

The count is computed over real customer rows, so it is the number that would actually be offered, not an estimate. exact: false means the scan hit its bound and the number is a floor.

Apple requires a signature on a promotional offer before StoreKit will accept it. Once an In-App Purchase signing key is provisioned, ask for one per customer:

bash
curl -X POST https://api.cashsdk.com/v1/apps/$APP_ID/catalog/offers/$OFFER_ID/signature \
  -H "Authorization: Bearer $CASHSDK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"appUserId": "user_123"}'

Without a key it answers { "signed": false } and the reason. It never returns a fabricated signature, because a bad one fails at the payment sheet.

Track redemptions#

Every redemption is written by the money path when the store reports the transaction, so the numbers are records rather than estimates.

In the dashboard. Offerings shows each offer with how many times it has been redeemed, and a feed of individual redemptions: the offer type, the product, the store's offer id, which customer redeemed it, what they paid, and when. A redemption the store reported with no app user attached reads unattributed rather than guessing.

Over the API.

bash
curl "https://api.cashsdk.com/v1/apps/$APP_ID/catalog/offers/redemptions?offerId=$OFFER_ID&limit=100" \
  -H "Authorization: Bearer $CASHSDK_SECRET_KEY"

Each row carries userId, productIdentifier, offerIdentifier, offerType, priceMinor, currency, environment, restoredEntitlement and redeemedAt.

As a webhook. Subscribe to offer.redeemed to react in real time. Its payload carries userId, offerId, offerType, offerIdentifier, productIdentifier, transactionId, restoredEntitlement, priceMinor and currency. restoredEntitlement is the one to watch: it means a churned customer just came back, which is the whole point of a win-back.

What a redemption rate can and cannot be#

For a targeted offer, the denominator is the audience: redemptions against the customers the offer targets. Read the count from offers:preview-audience and the redemptions from the endpoint above.

For an offer code, there is no honest denominator in CashSDK. The store issues the codes and does not report how many were handed out, so CashSDK can tell you exactly who redeemed and when, but not what share of your codes were used. App Store Connect and the Play Console report issuance on their side.