Events & webhooks

An event describes something that changed, such as Alice canceling renewal. A webhook is the HTTP message that carries that event from the provider to your backend.

Use events to keep your backend or data service informed while the app is closed. Cancellation turns renewal off; it does not remove Alice’s remaining paid time. Read current entitlements when you need an authoritative access decision.

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

Store → conforming backend → consumer HTTPS endpoint

To receive events in your product, start with the working receiver and setup guide. Run npm run demo:consumer in the example project to see accepted, repeated, and tampered deliveries before connecting a provider.

Request#

The emitter sends POST to a public HTTPS URL supplied directly by the consumer. The content type is application/json, and the body is one Commerce Protocol event document.

HeaderValueRole
openiap-signaturev1=<hex>, optionally repeated during rotationVerified against the exact body bytes
openiap-timestampUnix seconds at signingPart of the signed input
openiap-event-idThe eventId valueConvenience only; trust the signed body
openiap-delivery-idOne delivery-attempt chainOperational correlation only

Headers help route and inspect a delivery, but the signed body is the authority. Read eventId from the parsed body rather than trusting the convenience header.

Event vocabulary#

Known event types named by Protocol 1.0 are:

subscription.started, subscription.renewed, subscription.recovered, subscription.entered_grace_period, subscription.entered_billing_retry, subscription.expired, subscription.canceled, subscription.uncanceled, subscription.revoked, subscription.refunded, subscription.product_changed, subscription.price_changed, subscription.deferred, subscription.paused, subscription.resumed, entitlement.granted, entitlement.revoked

The schema value space is open: a later MINOR version may add another type. Read the event schema for the normative fields and apply only event types your consumer understands.

Verify the signature before parsing#

signature = "v1=" + lowercase_hex(
  HMAC_SHA256(secret, timestamp + "." + exactBodyBytes)
)
  1. Read the raw request bytes. Re-serializing JSON changes the signed input.
  2. Accept only when |now - timestamp| <= 300 seconds; otherwise reject it.
  3. Split openiap-signature on commas and accept any valid v1= value. Rotation may supply two signatures.
  4. Compare signatures in constant time.
  5. Parse and validate the verified body, then persist and deduplicate its eventId within the configured emitter/project scope before acknowledging delivery. An ID from one emitter must not suppress another emitter's event.

Delivery is duplicate-capable and unordered#

An emitter retries transient failures with exponential backoff and eventually stops and dead-letters an unaccepted delivery. A consumer may receive zero, one, or several copies. Acknowledge after durable ingestion and before slow downstream work. Keep those downstream effects idempotent on the stable eventId.

Consumer responseEmitter behavior
2xxDelivered
408, 429, 5xxRetry
3xxPermanent failure; do not follow
Other 4xxPermanent failure; do not retry
Timeout or connection errorRetry

Retries and independent queues can reorder events. Consumers use occurredAt to prevent an older snapshot from overwriting newer state, while still processing independent idempotent effects.

A subscription's active value is a snapshot at processedAt. Never grant access at or after its expiresAt; refresh current access when needed. A cancellation stops renewal and does not remove the remaining paid period. See ongoing access.

Destination safety#

Emitters accept public HTTPS destinations only. They reject embedded credentials and loopback, private, link-local, or unique-local addresses; validate every resolved address; and do not follow redirects. They connect only to a validated public address, by pinning it or verifying the connected peer before sending bytes.

Normative resources#