Profiles

A profile is a named set of responsibilities a provider promises to handle. It helps you answer “does this service do the part I need?”

For example, a purchase-checking service may offer verification. A backend that also owns user access needs account lifecycle and entitlements. A service can offer several profiles; you do not need a separate company for each one.

Four responsibilities#

Is the store evidence valid?

Checks a store purchase without assigning it to an app account.

A verification service can specialize in checking purchases while another service owns user access.

Compare the example and IAPKit

Same question: is the evidence valid?

Run the flow locally

openiap-commerce-protocol-example

verifyFixture recognizes one fictional purchase. A valid result saves it in SQLite with no user attached. You can watch Alice’s access stay locked after verification.

Read code: verifyPurchase
See the service implementation

IAPKit

The handler sends evidence to the Apple, Google, Meta, or Amazon verifier and returns the same isValid answer. Store-specific work stays behind this call.

Read code: verifyPurchaseVerdict
How to check this behavior

Example. The Verify step checks isValid and confirms that Alice still has no access. Run npm test. Inspect reference

IAPKit. The route suite checks unreachable verification and retry behavior using mocked store I/O. Inspect reference

Contract name: verification

Version 1.0. verifyPurchase

Whose purchase is it?

Connects purchases to your users and removes their identity when requested. Both binding and erasure are required.

Alice owns her purchase. Bob cannot take it. Deleting Alice also removes her identity from the provider’s records.

Compare the example and IAPKit

Delete the account in both implementations

Run the flow locally

openiap-commerce-protocol-example

Step 7 removes identity from purchases and event records atomically. The app erases delivered copies and remembers the deletion so late signed events cannot restore the user.

Read code: eraseUser
See the service implementation

IAPKit

eraseUser starts a project-scoped job. The worker removes identity from purchases, subscriptions, and events in batches, waiting for claimed deliveries before cleaning those events.

Read code: drainSubscriptionUserErasurePage
How to check this behavior

Example. Run bun verify-erasure.mjs. It checks active-account erasure, in-flight delivery, repeated jobs, late events, and restart. Inspect reference

IAPKit. Erasure request tests cover server authorization and repeated requests, including already completed jobs. Inspect reference

Contract name: accountLifecycle

Version 1.0. bindPurchase · eraseUser

What can this user use now?

Returns current subscription status and allowed products without exposing purchase tokens.

Alice keeps Premium after turning renewal off and loses access at the end of her paid time.

Compare the example and IAPKit

Same access rule, different storage

Run the flow locally

openiap-commerce-protocol-example

isEntitled reads the subscription state and expiry. SQLite holds the records; entitlements returns only products Alice can use now. Turning renewal off does not end paid access.

Read code: isEntitled
See the service implementation

IAPKit

isEntitledAt applies the same rule to Convex records. entitlementsV2 reads the current time and returns allowed products without exposing store purchase tokens.

Read code: isEntitledAt
How to check this behavior

Example. The state checks test before, at, and after expiry with a controlled clock. Run npm test. Inspect reference

IAPKit. Account-read tests check token omission, server authorization, and time advancing past expiry. Inspect reference

Contract name: entitlements

Version 1.0. subscriptionStatus · entitlements

How does my backend hear about changes?

Sends subscription and access changes as signed webhook messages, with retry rules.

Your backend or data service receives the same event meaning regardless of which compatible provider sent it.

Compare the example and IAPKit

Retries keep one inbox entry

Run the flow locally

openiap-commerce-protocol-example

A local worker signs and retries events. The included receiver verifies the signature and saves each event once in a separate SQLite inbox. Use it to understand both ends of delivery.

Read code: deliver / createReceiver
See the service implementation

IAPKit

The delivery worker sends signed events to registered backend HTTPS endpoints and records attempts. Your receiver owns duplicate handling; the example shows how to build that side.

Read code: deliverPendingEventsHandler
How to check this behavior

Example. Checks cover signatures, retries, tampering, and reopening the inbox. Run npm test or npm run demo:consumer. Inspect reference

IAPKit. Worker tests check signed requests, transport failures, and destination validation with test I/O. Inspect reference

Contract name: events

Version 1.0. Event and webhook obligations

Choose by responsibility, then check the promise

Every provider supports basic discovery, shared errors, and version rules. That common foundation is called core; it is not another optional profile.

The provider lists its complete profiles in capabilities. An unfinished profile must not be advertised. Conformance checks test the promise against the contract.

An analytics service that only receives events follows the receiver rules. It does not have to implement the provider’s events profile or a purchase backend.

Full obligations and independent profile versions: SPEC.md §3.