Purpose
Fires when the in-app purchase subscription state shifts — a user upgrades, downgrades, cancels, or the receipt is renewed. Sibling plugins (typically mam-inapp-purchase-manager) update entitlements, gate features, or dispatch notifications.
The action fires from inside phase_content of the phone-data pipeline when receipt validation detects a change.
Signature
do_action(
'mam_main_iap_subscription_has_changed',
int $user_id,
string $product_id,
string $previous_state,
string $new_state
);
| Parameter | Type | Description |
|---|---|---|
$user_id |
int | The user whose subscription changed |
$product_id |
string | The IAP product id |
$previous_state |
string | Previous state ('active', 'expired', 'cancelled', 'trial') |
$new_state |
string | New state |
Example: persist new entitlement state
add_action( 'mam_main_iap_subscription_has_changed',
function ( int $user_id, string $product_id, string $prev, string $new ) {
update_user_meta( $user_id, "iap_state_{$product_id}", $new );
update_user_meta( $user_id, "iap_state_{$product_id}_changed_at", current_time( 'mysql' ) );
// Notify on cancellation.
if ( $prev === 'active' && $new === 'cancelled' ) {
do_action( 'mam_notification_send_message', array(
'message_type' => 'mam-my-plugin-iap_cancelled',
'recipient_id' => $user_id,
'replacements' => array( 'product_id' => $product_id ),
) );
}
},
10, 4
);
Pipeline integration
mam-inapp-purchase-manager::manage_phone_data (priority 10) reads the user’s IAP receipts on every phone-data build. When receipt validation detects a state shift, it fires this action. Subscribers see the change in (near) real-time.
Gotchas
- Fires per-phone-data-request. A user with a long-running session may not see the action fire until they next launch the app or the cursor is invalidated.
- No retry. If your subscriber’s persistence fails, the action doesn’t fire again unless the state changes again.
product_idshapes vary by store. Apple uses reverse-DNS (com.example.subscription_monthly); Google uses arbitrary strings. Don’t assume a format.- Trial → active is a state shift. Don’t assume only “downgrades” fire.
- No transactional guarantee. Two simultaneous phone-data requests for the same user could both detect the same shift and both fire the action — guard your subscriber against double-application.
Related articles
- Phone data pipeline phases
- Hook: mam_get_phone_data_before_send
- Hook: mam_notification_send_message
Metadata
| Field | Value |
|---|---|
| Article type | Hook Reference |
| Plugin slug | mam-main |
| Applies to plugin version | 2.1.11+ |
| Hook type | action |
| Audience | PHP developer |
| Last verified | 2026-05-02 |
