On Speech Route the product identifiers matched and StoreKit still returned nothing. That sent me into the fetch for hours. The other version of the same empty list is worse in a quieter way: the ID in the binary is close, not equal, and the request looks healthy.
Close is not equal
A string that is almost the product ID will fetch successfully. You do not get a 404. You get []. I wrote the empty-list trap in Why StoreKit Returned No Products. This is the identifier half: copy the ID from App Store Connect into the binary, do not retype it from memory.
Where I look
App Store Connect, the product, the Product ID field. Then the constant or plist in the app. Character for character. A prefix from an old bundle, a missing dot, a subscription I created twice — those are the ones that waste a morning. They are not Guideline 2.1 in the notes. Review still sees an empty paywall, which is 2.1.
After the strings match
If they match and the list is still empty, I stop blaming the ID. I go to the Paid Applications Agreement, product metadata, and the sandbox account. The eight-rejection run that made this order a habit is in the first-submission post.