# TPInAppReceipt validates App Store receipts on the device

> TPInAppReceipt is a pure-Swift library that reads and validates Apple In App Purchase receipts locally, on device, without a server round trip. Its default validation covers the certificate chain, signature, hash and metadata, and it exposes both async and blocking entry points.

**tikhop/TPInAppReceipt** — Reading and Validating In App Purchase Receipt Locally.

- Repository: https://github.com/tikhop/TPInAppReceipt
- Stars: 717 · Forks: 96
- Language: Swift
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/tikhop-tpinappreceipt

## A receipt library for code that has no server to ask

Apple In App Purchase validation usually means a backend. Your app hands a receipt to your server, the server calls Apple, and the server decides whether the user paid. TPInAppReceipt takes a different route: it is a pure-Swift library for reading and validating Apple In App Purchase receipts locally, which means the check happens on the device that owns the receipt.

That positioning matters for a specific set of cases. A macOS app sold outside the App Store, a utility that unlocks features from a receipt it downloaded, or any app that wants to verify a receipt before it grants access can run the whole chain on device. The library description is exactly that narrow: "reading and validating Apple In App Purchase Receipt locally".

There is no server component, no network call and no account dashboard in the repository. The top level entries are a Swift package with Sources, Tests, a Package.swift and documentation folders. So the audience is developers writing Apple platform code who need a receipt decision to be local, either by design or because they have no backend at all. Teams that need a server authority should keep their server flow and treat this as a client-side layer at best.

## Reading a receipt from the bundle or from raw bytes

The decoding API has two entry points, and the difference between them is where the bytes come from.

```swift
import TPInAppReceipt

// Local receipt
let receipt = try await AppReceipt.local

// From raw data
let receipt = try AppReceipt.receipt(from: data)
```

The first reads the receipt that the system placed in the app bundle. The second takes bytes you already hold, which is the path for a receipt restored from a server, downloaded from somewhere else or forwarded between processes. Note the asymmetry in the signatures: the local read is async and throws, the raw data read throws but is not async.

Once a receipt exists, the README says you can access receipt fields, query purchases, check subscriptions and check introductory offer eligibility. The details are not in the README itself. The project points to a documentation catalog file, Sources/Docs.docc/Working%20with%20Receipt.md, for those specifics, which means the field level API is documented in the source tree rather than on the project page. That is a fair trade for a library of this size, but it does mean you clone the repository to read the real reference.

## The default validation chain covers four things at once

The default behaviour is summarized in a single line in the README: "certificate chain + signature + hash + metadata". That is four separate checks folded into one call, and it is the core of the library.

```swift
let result = await receipt.validate()

switch result {
case .valid:
    break
case .invalid(let error):
    print(error)
}
```

The result is a two case enum, so a caller either gets a valid receipt or an error value to inspect. The library's own essential reading list links Apple's pages on receipt validation, on receipt fields and on validating receipts locally, which tells you where the underlying expectations come from. It also links an article about the wrong ways to persist in-app purchase status in a macOS app, a warning that receipt validation and entitlement storage are separate problems.

Checks can be extended rather than only replaced. The README states that custom validators are supported via `@VerifierBuilder`, so a project can add its own verification steps to the default set. The details of that builder live in Sources/Docs.docc/Validating%20Receipt.md.

## An SPI escape hatch for code that cannot be async

The library is async first, and the blocking path is deliberately hidden behind an SPI import rather than offered as a plain public API.

```swift
@_spi(Blocking) import TPInAppReceipt

let receipt = try AppReceipt.local_blocking
let result = receipt.validate_blocking()
```

The underscore prefixed SPI attribute is Swift's convention for API that is public for a narrow audience but not part of the supported surface. The README describes it as being "For contexts where async is not available", which is a fair summary of the trade: you get a synchronous call, and you accept that the API can change without the usual deprecation guarantees. Names carry the same signal, with a `_blocking` suffix on both the receipt accessor and the validate call.

This is the single most important integration decision in the library. If your code path is already async, use `AppReceipt.local` and `validate()`. If it is not, and you cannot make it async, the blocking variant exists, with the documented caveat that it lives under SPI. The library also ships a .spi.yml at the root of the repository, which is the file Swift Package Manager uses to describe a package distributed through Swift Package Index.

## Adding the package with Swift Package Manager

Installation is documented for Swift Package Manager, with a stated set of requirements: Swift 6.0 or newer with Xcode 16 or newer, and platform floors of macOS 10.15, iOS 13, tvOS 13, watchOS 6.2 and visionOS 1.

Declare the dependency with the version range starting at the current release:

```swift
dependencies: [
    .package(url: "https://github.com/tikhop/TPInAppReceipt.git", from: "4.0.2")
]
```

Then add the package to the target that needs it:

```swift
.target(
    name: "YourTarget",
    dependencies: ["TPInAppReceipt"]
)
```

The `from:` clause pins the floor at 4.0.2 and lets SwiftPM resolve later 4.x releases under normal semantic versioning rules. The visionOS floor in the requirements list is worth noting, since it widens the platform range well beyond what an iOS-only library would claim.

The README also carries a CocoaPods platform badge next to the Swift Package Manager badge, but the installation section documents only the Swift Package Manager route. If you are on CocoaPods you will have to work out the pod integration yourself, since no podspec instructions are written down there.

## Local validation is not a server-side authority

The central limitation is the one the library name makes easy to miss. A receipt validated locally is validated by code running on a device the user controls. The README's essential reading links to Apple's guidance on receipt validation and to Apple's page on validating receipts locally, but the library does not claim, and cannot offer, that a local result matches what Apple's server would say about the same receipt at the same moment.

Practically that means three things. A receipt that has been refunded, revoked or consumed changes status on Apple's side, and a local check cannot see that change. A receipt obtained on another device, or restored after reinstall, needs the raw data path (`AppReceipt.receipt(from:)`) and a trusted place to fetch it from. And a determined user runs the validation on hardware they own, which is a different trust model from a server holding the keys.

There are also gaps that are simply not addressed anywhere in the project documentation. The README does not mention StoreKit 2, the App Store Server API, subscription renewal handling or grace periods. If your product needs to track an auto-renewing subscription across renewals rather than validate a single receipt, this library answers a narrower question than the one you have. Treat it as receipt parsing plus a local integrity check, not as a subscription entitlement service.

## Version 4 arrived with a migration guide and a MIT licence

The licence is MIT, which is about as permissive as software licences get, and the README states it plainly. Contributions are covered by a LICENSE file at the repository root. There is no clause to negotiate here, though as always the licence of the code you link alongside this library is a separate question for your legal team.

On maintenance, the repository is not archived and the last push was on 2026-07-20. The release list shows three tags in quick succession: 4.0.0 and 4.0.1 on 2026-02-04, then 4.0.2 on 2026-02-06. A major version bump on a Swift library usually means a source breaking change, and this one does come with a document named TPInAppReceipt 4.0 Migration Guide in the documentation catalog.

The upgrade cost for existing users is therefore real but bounded: you read one guide, you adjust call sites, and you rebuild. The repository also carries a CHANGELOG.md and CONTRIBUTING.md, plus a .swift-format configuration, which suggests a conventional Swift package layout rather than a script. The package targets the iOS family plus macOS, so nothing here runs on Android, Windows or the web.

## Conclusion

TPInAppReceipt suits iOS and macOS teams that want receipt checks to happen on the device, in an app that validates entitlements at launch, or inside a macOS utility that has no server. It does not suit a backend that must authoritatively confirm a purchase, and it offers nothing for stores other than Apple's. Before adopting it, confirm your toolchain meets Swift 6.0 and Xcode 16, check the platform floor of macOS 10.15, iOS 13, tvOS 13, watchOS 6.2 and visionOS 1, and read the TPInAppReceipt 4.0 Migration Guide if you are moving from v3.

## FAQ

### How do I validate an App Store receipt locally with TPInAppReceipt?

Read the receipt with `try await AppReceipt.local` or `try AppReceipt.receipt(from: data)`, then call `await receipt.validate()`. The result is a two case enum that is either valid or invalid with an error.

### What platforms and toolchains does TPInAppReceipt support?

The README lists Swift 6.0 or newer with Xcode 16 or newer, and platform floors of macOS 10.15, iOS 13, tvOS 13, watchOS 6.2 and visionOS 1.

### Can I use TPInAppReceipt in synchronous code?

Yes, through a blocking API imported with `@_spi(Blocking) import TPInAppReceipt`, which exposes `AppReceipt.local_blocking` and `validate_blocking()`. Because it sits behind an SPI import, it is not part of the normally supported public surface.

### Is TPInAppReceipt still maintained?

The repository is not archived and the last push was on 2026-07-20, with releases 4.0.0 and 4.0.1 on 2026-02-04 and 4.0.2 on 2026-02-06. It is published under the MIT licence.

## Sources

- [Issues](https://github.com/tikhop/TPInAppReceipt/issues)
- [License: MIT](https://github.com/tikhop/TPInAppReceipt/blob/master/LICENSE)
- [README](https://github.com/tikhop/TPInAppReceipt/blob/master/README.md)
- [Releases](https://github.com/tikhop/TPInAppReceipt/releases)
- [tikhop/TPInAppReceipt on GitHub](https://github.com/tikhop/TPInAppReceipt)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tikhop-tpinappreceipt
