Apple's 12-month commitment plans, decoded in your subscription notifications
Apple's new commitment plans — an annual subscription paid monthly — arrive as ordinary renewal events, indistinguishable from a regular monthly sub unless you decode two new payload fields. Juice Machine now surfaces the plan type and commitment progress on every notification, across every channel.
App Store Server Notifications Subscriptions iOS
The headline In-App Purchase feature from WWDC 2026 was 12-month commitment plans: customers subscribe to an annual plan but pay for it monthly — twelve instalments, one year of commitment. We covered the announcement in last week’s WWDC recap and said surfacing the new payload fields was on our list.
It shipped. Here’s what changed, and why it matters for anyone watching subscription events.
The payload problem
Apple didn’t add any new notification types for commitment plans. Every instalment arrives through App Store Server Notifications V2 as the same lifecycle events you already receive — DID_RENEW each month, DID_FAIL_TO_RENEW when a payment fails, and so on.
Which means a commitment-plan renewal is indistinguishable from a regular monthly subscription event unless you decode two fields added in ASSN 2.21.0:
billingPlanTypeinsignedTransactionInfo—BILLED_UPFRONTorMONTHLY. Regular subscriptions and commitment plans share every other field.commitmentInfo, also insignedTransactionInfo— which instalment this is (billingPeriodNumber, 1–12), how many there are in total (totalBillingPeriods), when the commitment ends (commitmentExpiresDate), and the committed price.
signedRenewalInfo carries a parallel pair — renewalBillingPlanType plus its own commitmentInfo — describing what happens when the current commitment period ends: the product, price, and billing plan the subscription renews onto.
Both live inside the JWS tokens nested in the notification payload, so if you handle V2 notifications yourself, this is another round of “update your decoders or silently lose the signal.”
Why the signal matters
The difference isn’t cosmetic. A DID_FAIL_TO_RENEW four payments into a twelve-month commitment is a different event from a month-to-month subscriber lapsing:
- The customer has committed to the full year — a billing failure mid-commitment is a recovery problem (expired card, billing retry), not a churn decision.
- Commitment expiry is the actual renewal decision point. “Payment 11 of 12” arriving in your channel is the cue that this customer decides next month whether to stay.
- Pricing differs: the committed instalment price and the post-commitment renewal price can diverge, and the renewal info now tells you both.
If your team triages billing failures from a Slack channel or pages on them via PagerDuty, you want that context in the message, not three clicks away in a dashboard.
What Juice Machine shows now
Subscription notifications now carry two extra labelled fields whenever the event belongs to a commitment plan:
- Plan — “12-month commitment, paid monthly” (the period count comes from the payload, so if Apple ever ships 6-month commitments, it’ll just say so).
- Commitment — “Payment 4 of 12”.
Regular subscriptions are unaffected — BILLED_UPFRONT is the implicit default for every subscription that existed before commitment plans, so we deliberately don’t label it. No noise on events that haven’t changed.
The fields render natively in every channel — Slack, Microsoft Teams, Discord, email, PagerDuty, and Telegram — because they plug into the same cross-channel field contract as everything else. Generic webhook consumers get the raw decoded values in enriched_data (billing_plan_type, commitment_period_number, commitment_total_periods, commitment_expires_at, commitment_price_milliunits, and the commitment_renewal_* family), and the full decoded set shows on each event’s page in the dashboard.
No setup needed. The decoding happens in the same credential-free JWS path as the rest of our V2 handling, so it works even if you’ve never connected App Store Connect API credentials. If your app starts selling commitment plans, the fields just appear.
If you roll your own
Decode signedTransactionInfo and signedRenewalInfo as usual (ES256, x5c chain to Apple Root CA G3), then read billingPlanType and commitmentInfo from each. Treat both as optional — they’re absent on everything that isn’t a commitment plan, and all of commitmentInfo’s fields are individually optional too. The authentication docs cover the JWS verification if you’re starting from scratch.
TL;DR
- Commitment plans reuse the existing ASSN V2 notification types — only new fields distinguish them (
billingPlanType,commitmentInfo, ASSN 2.21.0). - A mid-commitment billing failure is a recovery problem, not churn; payment 11 of 12 is the real renewal decision point. The fields are the signal.
- Juice Machine now renders Plan and Commitment fields on subscription notifications in every channel, with the raw values in generic webhook payloads — automatically, no configuration, no API credentials required.