App Store Connect webhooks vs App Store Server Notifications V2: which do you need?
Apple ships two completely different webhook systems for iOS developers, and the docs barely acknowledge each other. Here's what each one is for, how to tell which you need, and the awkward truth that most teams need both.
App Store Connect App Store Server Notifications iOS
Apple ships two completely different webhook systems for iOS developers, and the docs barely acknowledge each other. If you’ve ever tried to figure out whether your app needs “App Store Connect webhooks” or “App Store Server Notifications V2”, you’ve probably read both Apple docs pages five times and come out more confused than when you started.
Here’s the short version:
- App Store Connect Webhooks notify you about the app itself: builds being uploaded, review status changes, TestFlight events.
- App Store Server Notifications V2 notify you about purchases: subscription renewals, refunds, billing failures, IAP transactions.
They’re two separate systems with different URLs, different signing schemes, different payload formats, and different access controls inside App Store Connect. Most teams who care about either eventually need both. This post explains what each one is for, how to tell which you need, and the practical implications of running both.
What each system is actually for
App Store Connect Webhooks
These cover operational events about your app:
- A new build finishes processing in TestFlight (
buildBetaStateUpdated) - A build is uploaded to App Store Connect (
buildStateCreated) - An App Review state changes (
appStoreVersionStateUpdated—WAITING_FOR_REVIEW→IN_REVIEW→REJECTED/READY_FOR_SALE) - A tester submits TestFlight feedback (
betaFeedbackCreated)
You configure them in App Store Connect under Users and Access → Integrations → Webhooks. Authentication is HMAC-SHA256 with a shared secret per webhook — Apple signs requests with the secret and you verify the signature on receipt.
These events live in the same world as the App Store Connect API. If you’ve ever scripted anything against ASC — uploading builds with Transporter, querying the API for ratings — these webhooks are the push-based version of that surface.
App Store Server Notifications V2
These cover purchase events from the actual store:
- A user starts a new subscription (
SUBSCRIBED) - A subscription renews (
DID_RENEW) - A renewal attempt fails (
DID_FAIL_TO_RENEW) - A subscription is cancelled or expires (
EXPIRED, with subtypes forVOLUNTARY,BILLING_RETRY,PRICE_INCREASE,PRODUCT_NOT_FOR_SALE) - A refund is granted (
REFUND) - A consumable IAP is purchased (
ONE_TIME_CHARGE)
You configure them per app, under that app’s App Information → App Store Server Notifications page. Authentication is via JWS — Apple signs the payload with a key chained back to Apple’s Root CA G3, and you verify the certificate chain plus the signature on every event.
The payload is also notably nested: the outer event contains signedPayload, which decodes to a JWT that contains signedTransactionInfo and (for subscription events) signedRenewalInfo, which are themselves JWS-signed and need to be decoded the same way. Three layers deep before you see the actual data.
Which one do you need?
The simplest filter:
- You sell anything on the App Store (subscriptions, IAPs, one-time purchases) → you need App Store Server Notifications V2.
- You ship updates and have humans watching for App Review verdicts or tester feedback → you need App Store Connect Webhooks.
- You’re an indie dev shipping a paid app, or a SaaS company with an iOS client → you need both.
There’s no overlap. App Store Connect Webhooks never tell you about a purchase. App Store Server Notifications V2 never tell you about a build or a review. The categories don’t bleed into each other.
Why this is annoying
The two systems were built by different teams at Apple, years apart. App Store Server Notifications shipped with the original push-based IAP notifications years before Webhooks for ASC events were added as part of the App Store Connect API surface.
Symptoms of this split:
- The two webhook URLs are configured in completely different places inside ASC.
- One uses HMAC, the other uses JWS — so your signature verification code is twice the size.
- The payload formats are utterly different: ASC webhooks are plain JSON; ASSN V2 wraps everything in nested JWTs.
- Apple’s own documentation barely cross-references them. Search results for “App Store webhook” mostly hit ASC; search for “subscription notification” mostly hits ASSN.
If you wire up both yourself, you end up with two distinct codepaths, two sets of secrets to manage, two verification routines to keep current with Apple’s signing keys.
The transport difference
Both systems POST to a URL you control. Beyond that they diverge:
| App Store Connect Webhooks | App Store Server Notifications V2 | |
|---|---|---|
| Where configured | Users and Access → Integrations → Webhooks | Per-app → App Store Server Notifications |
| Signing | HMAC-SHA256, shared secret per webhook | JWS (ECDSA P-256 via x5c chain) |
| Payload shape | Plain JSON | Wrapped JWT containing nested JWTs |
| Retry | At-least-once, exponential backoff | At-least-once, exponential backoff over multiple days |
| Sandbox/prod | No sandbox concept — one URL | Configured separately for sandbox and production |
Apple does not guarantee event ordering. A DID_RENEW event for a transaction can arrive before the SUBSCRIBED event that started the subscription. Build for unordered, at-least-once arrival from day one.
What “handling them properly” actually means
For both systems, the work isn’t really receiving the POST. It’s everything around it:
- Verifying the signature correctly, every time, even when Apple rotates their signing keys.
- Decoding the nested JWTs for ASSN V2 — and surfacing the useful fields (product ID, original transaction ID, environment, expiration date, revocation reason) rather than just storing the raw blob.
- Enriching the events with data from the App Store Connect API. The raw notification tells you a transaction happened; it doesn’t tell you the product’s human name, the price in the customer’s currency, or the cumulative revenue per subscriber.
- Routing them to wherever your team actually lives — Slack, Teams, Discord, PagerDuty, email, a webhook into your internal system, all with channel-appropriate formatting.
- Tracking delivery so you know what got delivered, what failed, and what to retry.
- Filtering so the PagerDuty channel only pages on
DID_FAIL_TO_RENEWand the product channel only seesSUBSCRIBEDevents.
This is what Juice Machine does. We accept both webhook formats on the same URL (we auto-detect which one by inspecting the body), verify the JWS signature chain on every App Store Server Notification (App Store Connect webhooks carry no signature we can verify — they’re authenticated by your app’s secret per-app URL), decode the nested payloads, enrich events from the ASC API once you’ve connected API credentials, and forward them to Slack, Discord, Microsoft Teams, PagerDuty, Telegram, email, or any HTTP endpoint you control.
If you want to roll your own, the destinations docs describe what the payloads look like, the App Store Connect setup guide walks through where to paste the URL in App Store Connect, and the authentication docs cover how both formats are authenticated.
TL;DR
- Two systems, both called “Apple webhooks”, neither covers the other’s events.
- App Store Connect Webhooks = builds, reviews, TestFlight feedback. HMAC-signed.
- App Store Server Notifications V2 = subscriptions, refunds, IAPs. JWS-signed, three layers of nested JWTs.
- Most teams need both, configured separately, verified differently.
- Or use Juice Machine to receive both on one URL and forward them to wherever your team lives.