We’ve migrated our documentation to a new site, which means some URLs have changed.
Subscriptions

How Do I Troubleshoot External Subscription Receipt Verification in Piano?

How External Receipt Verification Works in Piano

Piano supports two Apple verification approaches via the same /publisher/conversion/external/create endpoint:

  • v1 (legacy): Submit a Base64-encoded receipt from appStoreReceiptURL.

  • v2 (App Store Server API): Submit a JWS transaction string (transactionData) or a transactionId. Requires additional configuration in your existing Apple EVT configuration — see Apple App Store Server API (v2): Configuration below.

Regardless of approach, the general flow is:

  1. Your app (or middleware/backend) collects proof of purchase

    • v1 (Apple iOS): Read the receipt from appStoreReceiptURL, Base64-encode it, and send it to your backend.

    • v2 (Apple): Obtain the JWS transaction string or transactionId from the App Store Server API in your backend.

  2. Your backend submits the purchase to Piano

    • Use /publisher/conversion/external/create

    • Include the receipt or transaction data in the request fields and set check_validity=true to force validation on submission.

  3. Piano validates with the external provider

    • v1: Receipt must contain valid product_id and expiration data (for example, expires_date / expire_date, per Apple's receipt format).

    • v2: Piano validates the JWS transaction or looks up the transaction by ID via the App Store Server API.

    • If valid, Piano creates or updates the conversion and access through the receipt's or transaction's expiration date.

If the receipt or transaction submission never reaches Piano (or fails before verification), the transaction may not appear in Piano at all — resulting in "user paid but has no access" scenarios that require investigation and re-submission.

Apple App Store Server API (v2): Configuration

For Apple's App Store Server API integration, Piano supports two environments: Sandbox (testing and development) and Production (live App Store transactions). Xcode local testing is currently not supported.

Initial Configuration

  • Proceed as described in this documentation.

  • You do not need to create a separate configuration in Piano for the v2 integration if you already have an existing Apple EVT configuration. Add the new v2 configuration parameters to your existing configuration in order for the Piano endpoint to start accepting both receipt types (v1 and v2).

  • Once you add the new parameters to the configuration, you will not be able to remove them — even though it is possible to save the configuration without them.

Where to Check Verification Status in the Piano UI

User Profile

  1. Open the user's profile.

  2. Go to Subscriptions.

  3. You might see a badge with a number next to the name of the Subscriptions tab.This indicates the number of external subscriptions, that have or have not been verified. If a subscription has not been verified, you will see a tooltip with the following information: This user’s external subscription(s) did not verify successfully.

Logs and Reporting

  • Use the External term log (in Piano Management + Billing) to confirm whether Piano received the external conversion attempt.

  • Use Subscription logs and filter by term type and term name to narrow down impacted terms.

  • Use access reporting to confirm whether access was actually granted or extended for the expected term.

Common Causes and How to Fix Them

Receipt/Purchase Token Already Linked to Another User (Ownership Conflict)

Symptoms:

  • Receipt submits successfully but does not grant access to the expected user

  • Often happens when multiple accounts are used with the same Apple ID or store account

Why it happens: When Enforce uniqueness is enabled in your External API configuration, a given unique_id/receipt/token can only be redeemed for one Piano user (UID).

Resolution options:

  • Ask the user to sign in to the account that already owns the subscription.

  • If appropriate for your business rules, adjust the Enforce uniqueness setting in the External API configuration.

  • If the receipt was attached to the wrong user:

    1. Identify the user currently linked to the receipt/token (often requires backend/internal investigation).

    2. Cancel access/conversion for the incorrect user (or allow it to expire).

    3. Re-submit the receipt or transaction for the correct user (see Manually Re-Submit a Receipt or Transaction).

product_id Mismatch Between the Store Receipt and the Piano External Term

Symptoms:

  • Receipt or transaction is valid in the store, but Piano rejects it for the configured term

Why it happens: The product_id in the receipt or transaction must match exactly what is configured for the external term in Piano (including case sensitivity). Typos, outdated product IDs, or using the wrong term can cause this.

Resolution:

  1. Confirm the product_id in the receipt or transaction data.

  2. Update the external term configuration to include the correct product_id.

  3. Re-submit with check_validity=true.

Malformed or Missing Receipt Data

Symptoms:

  • Verification fails immediately

Why it happens: Receipt payload is empty, truncated, incorrectly encoded, or altered in transit. Applies to v1 receipt submissions.

Resolution:

  • Ensure the iOS receipt is:

    • Read from appStoreReceiptURL

    • Base64-encoded

    • Sent without alteration through middleware (watch for URL encoding/decoding or character changes)

  • Re-capture the full Base64 receipt and re-submit.

  • If you have migrated to v2, submit a JWS transaction string (transactionData) or transactionId instead — see Manually Re-Submit a Receipt or Transaction.

Missing Transaction Identifiers in the Receipt (Apple)

Symptoms:

  • Verification or linking issues; Piano can't reliably associate or replay the receipt

  • Investigation shows incomplete receipt content

What to check: Confirm the receipt contains valid transaction_id and original_transaction_id.

Resolution:

  • Ensure you are submitting the complete, current Apple receipt (Grand Unified Receipt format, as applicable in your implementation).

  • Re-submit the full Base64 receipt (v1), or switch to submitting the transactionId directly via the v2 approach.

Receipt Was Never Submitted to Piano (or Submission Failed)

Symptoms:

  • The user was charged in the store, but there is no corresponding conversion or record in Piano

  • No entry in external term logs for the time of purchase

Resolution:

  • Check application or middleware logs to confirm a successful call to /publisher/conversion/external/create with check_validity=true.

  • Fix integration issues and re-submit the receipt (v1) or transaction data (v2).

Renewals Not Updating (Apple Subscriptions)

Symptoms:

  • Subscription shows expired in Piano but renewed in Apple

  • Access does not extend after renewal

Why it happens: App Store Server Notifications help keep subscriptions current. If notifications are disabled, misconfigured, or pointing to the wrong URL version, renewals can be missed.

Resolution:

  • Verify App Store Server Notifications are enabled and correctly configured in App Store Connect.

  • If you have configured v2 notifications, confirm the App Store Server Notifications URL in App Store Connect has been updated to the v2 URL from your Piano Apple configuration — see App Store Server Notifications below.

  • If an update was missed, re-verify or re-submit the receipt or transaction and confirm the latest expiration details are reflected.

The legacy Apple EVT integration (v1) may not support Apple grace periods. If a subscription is in a grace period, it may not be represented in Piano unless you are using the v2 integration, which supports that behavior.

Manually Re-Submit a Receipt or Transaction

Manual re-submission is useful when the original request failed, configuration was corrected (for example, product_id mapping), or you need to trigger a new verification after resolving conflicts. Both v1 and v2 approaches use the same endpoint.

v1 — Base64 receipt:

Include the Base64-encoded receipt in the receiptData field:

curl --location 'https://sandbox.piano.io/api/v3/publisher/conversion/external/create' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --header 'api_token: xxx' \
  --data-urlencode 'aid=xxx' \
  --data-urlencode 'uid=xxx' \
  --data-urlencode 'term_id=xxx' \
  --data-urlencode 'check_validity=true' \
  --data-urlencode 'fields={ "receiptData":"..." }'

v2 — JWS transaction string:

Pass the JWS transaction from Apple in the transactionData field. Piano will validate the JWS transaction and create or update the conversion based on the transaction data. For detailed terms on external services, refer to Piano External Service Terms. For Apple's documentation on JWS transactions, see JWS Representation.

curl --location 'https://sandbox.piano.io/api/v3/publisher/conversion/external/create' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --header 'api_token: xxx' \
  --data-urlencode 'aid=xxx' \
  --data-urlencode 'uid=xxx' \
  --data-urlencode 'term_id=xxx' \
  --data-urlencode 'check_validity=true' \
  --data-urlencode 'fields={ "transactionData":"eyJhb..." }'

v2 — Transaction ID only:

If you do not have a JWS transaction string, you can pass the transactionId alone, which represents the transaction you want to convert:

curl --location 'https://sandbox.piano.io/api/v3/publisher/conversion/external/create' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --header 'api_token: xxx' \
  --data-urlencode 'aid=xxx' \
  --data-urlencode 'uid=xxx' \
  --data-urlencode 'term_id=xxx' \
  --data-urlencode 'check_validity=true' \
  --data-urlencode 'fields={ "transactionId":"2000...." }'

Expected result for all approaches:

  • HTTP 200 response when accepted and verified

  • The "external subscription did not verify successfully" banner should disappear

  • The subscription should become Active and the correct resources should appear under access

Apple-Specific: Verify a Receipt Directly with Apple (Diagnostic)

To validate whether a v1 receipt itself is acceptable to Apple (independent of Piano), you can POST to:

https://buy.itunes.apple.com/verifyReceipt

Include:

  • receipt-data (Base64)

  • password (the shared secret configured for your Apple external API integration)

Review latest_receipt_info (and key fields such as purchase_date, expires_date, and cancellation_date) to confirm the subscription's current state.

For v2 integrations, use the App Store Server API directly to look up transaction status.

App Store Server Notifications

If you use App Store Server Notifications, after adding the new v2 fields to your Apple configuration in the Piano Dashboard (Manage > External APIs), you must update the App Store Server Notifications URL in App Store Connect. Copy the App Store Server Notification URL from the Piano Apple configuration and update it as follows:

  1. Log in to App Store Connect.

  2. Go to your app's App Information page.

  3. Edit the App Store Server Notifications URL (Version 2).

If you choose to use Version 2 notifications, you cannot revert to Version 1.

Google Play Notes

  • Ensure your Google configuration (service account details / public key) is correct in the External API settings.

  • Piano does not validate digital consumable in-app purchases from Google Play; verification applies to non-consumable and subscription types.

Preventive Monitoring Recommendations

  • Regularly export and review users to catch issues early.

  • Consider adding operational alerting (for example, webhooks in your own systems) whenever external verification fails, so your team can remediate before users report access problems.

What to Collect for Faster Investigation

When escalating to Piano Support, provide:

Information

Details

Piano UID

The affected user's Piano user ID

External term ID

The external term's pub_id

Receipt / purchase token / transaction data

Full Base64 receipt (Apple v1), JWS transaction string or transaction ID (Apple v2), or purchase token payload (Google)

Purchase timestamp

From store reporting

Verification status message

As shown in the user's Subscriptions tab

Integration version

Whether you are using v1 (receipt-based) or v2 (App Store Server API)

Last updated: