Use this page with AI
Copy this into your coding assistant and add your request.
Read https://openiap.dev/commerce-protocol/getting-started and https://openiap.dev/llms.txt. Follow the reading instructions, detailed reference, and linked guides relevant to my task before making changes.
Inspect my existing project and reuse its framework and conventions. Ask me for missing product decisions. Implement the requested behavior and run the applicable checks.
Show the working result, the commands and actual test results, and any remaining limitations. Keep your explanation brief.
My request: [describe what customers should be able to do]Follow one purchase.
Alice buys Premium in your app. Follow her purchase from the store’s payment screen to the moment she can open paid content.
At each step, compare two implementations: openiap-commerce-protocol-example makes the flow visible with a local dashboard and fictional purchases; IAPKit connects the same contract to store services. Follow the behavior first, then open either implementation’s code when you need it.
IAPKit accepts Apple, Google and Amazon subscription notifications; Amazon notifications refresh RVS before changing access. Its Amazon subscription lifecycle is in beta; validate the documented live store transitions before production use. Amazon and Horizon ownership is also rechecked on each entitlements read. The example simulates these store responses locally. Another provider can work differently; its capabilities say what it supports. Choose your store below to follow the matching path.
Alice chooses Premium.
Alice chooses Premium on your paywall. Your app opens Apple’s purchase screen through its OpenIAP client library. A successful purchase gives your backend the evidence it needs to ask the store for verification.
The app sends the evidence to your backend using Alice’s signed-in session. A pending, canceled, or failed purchase creates no new access.
Where the store result goes
openiap-commerce-protocol-example
The purchase mapper converts Apple, Google, Amazon, and Horizon client results into verification input on your backend. Its demo checks the mapping without making a store purchase.
Read code: toVerifyPurchaseInputIAPKit receives that store evidence through verifyPurchase. The paywall, store purchase screen, and app login stay with your app; IAPKit does not replace them.
Read code: verifyPurchaseHow to check this behavior
Example. runBridgeDemo checks all four store mappings and requires a separate store identity for Amazon and Horizon. Run npm run demo:bridge. Inspect reference
IAPKit. Route tests cover evidence parsing and errors with mocked store calls; they do not perform a mobile purchase. Inspect reference
See the same app switch between the example and IAPKit →
Ready to build? Choose your services and build it →
Use these steps when wiring a backend or checking your AI’s code.
1. Install the contract#
Use your favorite package manager.
npm install @hyodotdev/openiap-commerce-protocolThe package gives your backend the contract, schemas, and conformance tools. Obtain a compatible provider endpoint and credentials separately.
To explore first, open the recorded purchase flow, or run the reference dashboard. To implement your own provider, give your AI the build brief.
2. Connect to a provider#
Obtain the base URL, supported store configuration, and the verification and server credentials from your provider. A provider issues its own credentials; OpenIAP has no registration service. Each request sends one as Authorization: Bearer <credential>. Keep the server credential in your backend. These shell examples assume curl and jq.
export COMMERCE_BASE_URL='https://your-provider.example'
# Set these through your local secret manager or environment.
# COMMERCE_VERIFICATION_TOKEN: the verification credential
# COMMERCE_SERVER_TOKEN: the server credential
curl --fail-with-body "$COMMERCE_BASE_URL/commerce/v1/capabilities"Inspect profiles, bindings, and the selected store’s implementation support. This flow needs verification, accountLifecycle, entitlements, and rest at compatible major versions. An events-only descriptor does not establish operation support. See capabilities for the full descriptor.
3. Verify, bind, and read access#
Save the Apple evidence below as evidence.json; it follows the store chosen above. Replace the illustrative JWS with the StoreKit transaction JWS the app received. Your provider must be configured for that app and store environment.
{
"store": "apple",
"apple": {
"jws": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0cmFuc2FjdGlvbklkIjoiMjAwMDAwMDEyMzQ1Njc4OSIsInByb2R1Y3RJZCI6InByZW1pdW0ubW9udGhseSJ9.MEUCIQDkQmFrb2Zha2VzaWduYXR1cmVmb3JleGFtcGxlAiBkb2N1bWVudG9ubHlmYWtlc2lnbmF0dXJldmFsdWU"
}
}curl --fail-with-body "$COMMERCE_BASE_URL/commerce/v1/purchases/verify" \
-H "Authorization: Bearer $COMMERCE_VERIFICATION_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @evidence.jsonA successful response carries isValid. A store rejection is still a successful operation with isValid: false. VERIFICATION_FAILED means no verdict was obtained; retry according to your failure policy. Neither a verdict nor its advisory state identifies the app user or answers their current access.
Your backend authenticates the user and checks that it may associate this purchase with them. Select COMMERCE_USER_ID from that session and ownership policy. A user ID submitted by the app and possession of a receipt are not sufficient authorization.
# Run on your backend; COMMERCE_USER_ID is selected by your authenticated handler.
jq --arg userId "$COMMERCE_USER_ID" '. + {userId: $userId}' \
evidence.json > binding.json
curl --fail-with-body "$COMMERCE_BASE_URL/commerce/v1/purchases/bind" \
-H "Authorization: Bearer $COMMERCE_SERVER_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @binding.jsonContinue after bound: true. For a store the provider integrates, a bound: false result intentionally does not distinguish unknown evidence from a purchase belonging to someone else; other stores fail with UNSUPPORTED_STORE instead. Do not transfer the binding or grant access on that result; use your provider’s recovery process. The association can remain bound even when the subscription is expired.
curl --fail-with-body --get "$COMMERCE_BASE_URL/commerce/v1/entitlements" \
-H "Authorization: Bearer $COMMERCE_SERVER_TOKEN" \
--data-urlencode "userId=$COMMERCE_USER_ID"Gate product-specific features on membership in productIds. An empty list is a complete answer with no current access. An operation error is not an empty list. The subscriptions array contains the tokenless records supporting that answer. For a simple “any active subscription?” check, use subscriptionStatus.active.
4. Handle changes after purchase#
If the provider declares events, register an HTTPS backend destination using its management interface and exchange a webhook secret. Destination registration is provider-specific. Follow the receiver steps to authenticate and persist each delivery before acknowledging it.
Use events to trigger an authoritative entitlement refresh when you cannot safely correlate purchases, especially across multiple subscriptions or product changes. Without events, use bounded server reads at the points your application needs a current answer. SPEC.md §2.3 defines when a subscription grants access.
When an authenticated user deletes their account, call eraseUser from your backend. The provider erases its own identity records; arrange erasure separately for copies already delivered to your backend and connected services.
jq -n --arg userId "$COMMERCE_USER_ID" '{userId: $userId}' > erasure.json
curl --fail-with-body "$COMMERCE_BASE_URL/commerce/v1/users/erase" \
-H "Authorization: Bearer $COMMERCE_SERVER_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @erasure.jsonA 202 with accepted: true acknowledges the request. When the answer carries a job status, send the same request again to read the job's progress: repeating it is safe and reports the current job. The provider's cleanup is finished at completed.
Continue with the operation reference, GraphQL binding, or provider implementation guide.
Receive subscription changes#
Connect an existing backend with the ready receiver. It verifies the signature, validates the event, and saves it once in SQLite. You do not need to build a commerce provider or a store adapter.
If your service only consumes events, this section is all you need. The purchase and access operations above belong to the app backend and its chosen provider; your service can keep its existing backend.
The example repository includes the receiver and executable checks. AI can reuse it to demonstrate signed delivery, retries, tamper rejection, and preserved inbox entries after reopening storage.
Run the receiver example locally
Clone the repository, install Bun for its runtime, then install dependencies and run the demo. This fixture check requires no credentials or external services.
Use your favorite package manager.
npm installnpm run demo:consumerConnect the receiver to your provider
Set COMMERCE_WEBHOOK_SECRET to your provider’s signing secret, then run npm run consumer. The ready endpoint is http://127.0.0.1:5182/webhooks/commerce. Put it behind your HTTPS reverse proxy, forwarding the exact body bytes to that local address, and register the HTTPS URL with your provider. The receiver accepts the public Host header forwarded by the proxy; its signature check authenticates the sender.
Use one emitter/project and signing key per receiver database. consumer.sqlite is the durable inbox; set COMMERCE_INBOX_PATH for your persistent storage path. To embed the same Fetch-compatible handler in your server, use createReceiver from webhooks.mjs.
The inbox preserves the signed event for your existing processing pipeline. Transaction and price fields are optional; a missing price is unknown. Each lifecycle event is not necessarily a new charge. Keep financial calculations in your business logic.