# go-iap verifies app store receipts for Apple, Google, Amazon, Huawei and Microsoft

> go-iap is a Go library set that checks purchase receipts and subscription status against five app stores, including the App Store Server API with a sandbox fallback client. It suits backend teams that need one receipt verification path instead of five hand-written integrations, and it targets Go 1.26 or newer.

**awa/go-iap** — go-iap verifies the purchase receipt via AppStore, GooglePlayStore, AmazonAppStore and Huawei HMS.

- Repository: https://github.com/awa/go-iap
- Stars: 1,062 · Forks: 257
- Language: Go
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/awa-go-iap

## One receipt verifier per storefront, each in its own package

go-iap is a Go module that verifies purchase receipts against the major app stores. The README describes it as verifying the purchase receipt via AppStore, GooglePlayStore, Amazon AppStore, HMS or MicrosoftStore, and the repository layout confirms that structure: appstore/, playstore/, amazon/, hms/ and microsoftstore/ sit at the top level next to go.mod.

The use case is narrow and specific. A mobile app sells something, the operating system reports a purchase to the client, and the client sends the receipt to your server. The server then has to ask the store whether that receipt is genuine, and grant the entitlement if it is. Every store does this differently: different endpoints, different signing schemes, different response shapes, different error semantics. go-iap exists to put those differences behind one Go API per store, so the payment code in your application does not have to grow five branches.

The module targets Go 1.26.0, and the README badge states Go 1.26 or newer. The dependency list is small and tells you what the library actually does. golang-jwt/jwt/v5 handles the JWT signing that Apple's App Store Server API requires, golang.org/x/oauth2 and google.golang.org/api cover Google service account authentication, google/uuid handles identifiers, stretchr/testify and go.uber.org/mock are for tests. There is no database layer, no HTTP framework and no queueing dependency, which is what you want from a library that sits inside a billing path.

The module publishes GoDoc pages per store, so the API surface for each is documented separately: appstore, appstore/api, playstore, amazon, hms and microsoftstore.

## The verifyReceipt endpoint is deprecated, the Server API is the path forward

The first thing to notice is a deprecation notice attached to the most commonly copied example. The README states that the verifyReceipt API has been deprecated as of 5 Jun 2023 and asks you to use the App Store Server API instead. The classic quick start still shows the old call:

```go
import(
    "github.com/awa/go-iap/appstore"
)

func main() {
	client := appstore.New()
	req := appstore.IAPRequest{
		ReceiptData: "your receipt data encoded by base64",
	}
	resp := &appstore.IAPResponse{}
	ctx := context.Background()
	err := client.Verify(ctx, req, resp)
}
```

If you copy that snippet into a new service in 2026, you are building on a retired endpoint. The supported route is the App Store Server API, which authenticates with a private key rather than a shared secret, and returns typed transaction and subscription objects.

Setting up that client requires a key file, a key ID, a bundle ID and an issuer identifier, all of which come from App Store Connect:

```go
c := &api.StoreConfig{
	KeyContent: []byte(ACCOUNTPRIVATEKEY),
	KeyID:      "FAKEKEYID",
	BundleID:   "fake.bundle.id",
	Issuer:     "xxxxx-xx-xx-xx-xxxxxxxxxx",
}
```

The private key is a .p8 certificate in PEM form, and the README points at Apple's documentation for generating and downloading it. This is a meaningfully different security model from the old secret based flow, and it is worth reading the Apple page before you rotate anything in production.

## NewStoreClientWithSandboxFallback retries production against sandbox on 4040010

The most interesting piece of engineering in the repository is the sandbox fallback client, and it exists because of a specific Apple behaviour.

Apple splits the App Store Server API into two hosts. Production is https://api.storekit.itunes.apple.com/ and sandbox is https://api.storekit-sandbox.itunes.apple.com/. The README gives a manual procedure for figuring out which one owns a transaction: call production, and if it succeeds the transaction is production; if you get error code 4040010, meaning TransactionIdNotFoundError, call sandbox; if that also fails with 4040010, the transaction exists in neither environment.

api.NewStoreClientWithSandboxFallback automates exactly that procedure. It returns an *api.StoreClientWithSandboxFallback, which satisfies api.StoreAPIClient, so it is a drop-in replacement for a client built with api.NewStoreClient. The reason it matters: an app distributed through App Review or TestFlight is a production build whose purchases are made in Sandbox, so the production host cannot find those transactions.

The retry covers the lookups Apple documents as returning TransactionIdNotFoundError, which the README lists as GetTransactionInfo, GetALLSubscriptionStatuses, GetTransactionHistory, GetRefundHistory and GetAppTransactionInfo. Every other method behaves exactly as it does on StoreClient. There is a second trigger, a 401 response, because that is how the store refuses an app that has never shipped there, and the README notes an App Store Commerce Engineer confirmed that behaviour.

The client also exposes the status code so you can handle rate limiting yourself:

```go
var withStatus interface{ StatusCode() int }
if errors.As(err, &withStatus) && withStatus.StatusCode() == http.StatusTooManyRequests {
	// back off
}
```

Nothing else is retried. The README is direct about the consequence: 401 is overloaded, so reach for this client only where a Sandbox answer is acceptable, and NewStoreClient never falls back.

## Each other store gets a small constructor and one verify call

The remaining stores are far less complicated than Apple, and the README shows one method each.

Google Play uses a service account JSON key, since the developer console issues one for in-app billing:

```go
client := playstore.New(jsonKey)
ctx := context.Background()
resp, err := client.VerifySubscriptionV2(ctx, "package", "purchaseToken")
```

Amazon App Store takes a developer secret and a user ID with a receipt ID:

```go
client := amazon.New("developerSecret")

ctx := context.Background()
resp, err := client.Verify(ctx, "userID", "receiptID")
```

Huawei Mobile Services takes a client ID, a client secret and two site URLs, and the README notes that empty orderSiteURL and subscriptionSiteURL values default to AppTouch German, with a pointer to the Huawei reference for details:

```go
client := hms.New("clientID", "clientSecret", "orderSiteURL", "subscriptionSiteURL")
ctx := context.Background()
resp, err := client.VerifySubscription(ctx, "purchaseToken", "subscriptionID", 1)
```

The pattern is consistent: a constructor that takes credentials, and a verify method that takes the identifiers the store issued. The Microsoft Store package exists in the repository and has its own GoDoc page, but the README does not show a usage snippet for it, so the exact call signature is something to read from the package documentation rather than assume.

One point about Play naming worth flagging: the method is VerifySubscriptionV2, which implies a v1 exists alongside it, and the README does not discuss when to use which.

## The Makefile keeps a mock generation step and a race enabled coverage target

The build tooling is minimal, and the two targets that carry information are generate and cover.

The generate target clears the mock directories for both the appstore and playstore packages before running go generate across the module. That is a strong signal about how the project is tested: rather than writing mock servers by hand, the store clients are mockable at the interface level, which is why go.uber.org/mock appears as a dependency alongside stretchr/testify.

The cover target runs the test suite with the race detector, in atomic coverage mode, writing a coverage profile:

```bash
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
```

Race detection in a billing library is not decorative. A verification client that runs concurrently across many requests is exactly the place where a data race would produce intermittent, hard to reproduce entitlement errors.

The default target chains setup and cover, where setup fetches the cover tool, fetches an appengine urlfetch package and updates the module dependencies. The update target runs go get -v -u across the module and then go mod tidy, which is the standard dependency refresh path and, given the pinned versions in go.mod, the mechanism by which store API changes get pulled in.

The unit test workflow is a separate GitHub Actions job, and the Makefile's PHONEY spelling is a typo that appears in every target, harmless but worth knowing if you script around it.

## MIT licensed, released roughly weekly, and moving to newer Go versions

The licence is MIT, which is the permissive end of the scale and carries no copyleft obligations for code that imports the library. The LICENSE file in the repository is the authoritative text, and this is a licensing statement rather than legal advice.

The release cadence is fast and regular. v1.59.0 landed on 2026-10-13, while v1.58.0 and v1.57.0 both landed on 2026-09-24, and the last push to the repository was on 2026-10-13 as well. The repository is not archived.

That cadence has a practical side. A library whose job is tracking five storefront APIs will keep moving as those APIs change, so pinning to a specific version and reading the changelog before bumping is the sane routine. There is no compatibility promise in the README, and the store APIs themselves can change underneath you, which means the risk in this dependency is not the library breaking but the store changing its rules and the library needing a release to match.

The go.mod also shows the indirect dependency weight that comes with talking to Google. cloud.google.com/go/auth, google.golang.org/grpc, google.golang.org/api, google.golang.org/protobuf and the OpenTelemetry instrumentation packages all come along for the Play Store integration. If you only need Apple and never import playstore, Go's module pruning keeps most of that out of your build.

## Conclusion

Adopt go-iap if you run a Go backend that has to confirm purchases for subscriptions or consumables on Apple, Google, Amazon, Huawei or Microsoft storefronts, and if you are willing to manage each store's credentials and signing keys. Do not adopt it if you want a client-side purchase flow, since the library is a server side verifier, and do not keep the deprecated verifyReceipt path alive. Before you wire it up, confirm your Go toolchain is 1.26 or newer, decide whether to use api.NewStoreClientWithSandboxFallback rather than api.NewStoreClient, and check which store credentials you will need to issue and rotate.

## FAQ

### How do I add go-iap to a Go project?

The README shows one go get per store package, for example go get github.com/awa/go-iap/appstore, plus the playstore, amazon, hms and microsoftstore equivalents. The module path is github.com/awa/go-iap and it requires Go 1.26 or newer.

### What should I use instead of the deprecated verifyReceipt call for Apple?

The README notes that the verifyReceipt API has been deprecated as of 5 Jun 2023 and directs you to the App Store Server API. That path uses api.NewStoreClient with a StoreConfig holding your .p8 private key content, key ID, bundle ID and issuer.

### What does api.NewStoreClientWithSandboxFallback do differently?

It queries the production App Store Server API host first and retries against the sandbox host when production reports TransactionIdNotFoundError, which is the case for TestFlight and App Review builds. The retry applies to GetTransactionInfo, GetALLSubscriptionStatuses, GetTransactionHistory, GetRefundHistory and GetAppTransactionInfo, and the client also falls back on a 401 response, so the README advises using it only where a Sandbox answer is acceptable.

### How do I verify a Google Play purchase with go-iap?

Read the service account JSON key for your app and pass it to playstore.New, then call VerifySubscriptionV2 with the package name and the purchase token, for example client.VerifySubscriptionV2(ctx, "package", "purchaseToken"). The key is prepared in the Google developer console.

### Which stores does go-iap support and what licence is it under?

The README states it verifies receipts via AppStore, GooglePlayStore, Amazon AppStore, HMS or MicrosoftStore, each with its own package in the repository. The project is licensed under MIT.

## Sources

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

---

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